看完WriterAppender的代码之后,我们就随便挑选两个稍微简单的ConsoleAppender和FilterAppender来看看,我们真正平时在使用的Appender是怎么实现的。首先是ConsoleAppender:
public void activateOptions() {
if (follow) {
if (target.equals(SYSTEM_ERR)) {
setWriter(createWriter(new SystemErrStream()));
} else {
setWriter(createWriter(new SystemOutStream()));
}
} else {
if (target.equals(SYSTEM_ERR)) {
setWriter(createWriter(System.err));
} else {
setWriter(createWriter(System.out));
}
}
super.activateOptions();
}
在这段代码中,我们可以看到另一个我们之前并没有介绍的属性follow,这个属性的作用非常简单,如果follow为true,那么就根据我们设置的target属性(还记得ConsoleAppender的target属性么?)来选择重新创建一个SystemErrStream或者SystemOutStream,如果follow为false,就是使用当前的System.out或System.err来输出。在这个createWriter方法中,我们就只看一句代码就够了:
retval = new OutputStreamWriter(os, enc);
非常简单,就是把传进来的System.out或者System.err,和encoding(还记得WriterAppender上面的encoding属性么?)包装成一个OutputStreamWriter。
另外,这个类也没有再去实现append方法或者subAppend方法,而直接是使用的WriterAppender去执行的。同时,看完代码之后,我们在使用ConsoleAppender设置的那些属性到底是怎么起作用的,也就非常明显了。
再来个例子,看看FileAppender,同理,还是先找acitivateOption再找append或者subAppend:
public void activateOptions() {
if (fileName != null) {
try {
setFile(fileName, fileAppend, bufferedIO, bufferSize);
} catch (java.io.IOException e) {
errorHandler.error("setFile(" + fileName + "," + fileAppend + ") call failed.", e,
ErrorCode.FILE_OPEN_FAILURE);
}
} else {
LogLog.warn("File option not set for appender [" + name + "].");
LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
}
}
很简单,首先检查必须设置文件名,这里的fileName即是我们设置的file属性得到的;如果没有问题,则使用setFile方法创建文件和Writer,我们来看看setFile的代码:
public synchronized void setFile(String fileName, boolean append, boolean bufferedIO,
int bufferSize) throws IOException {
LogLog.debug("setFile called: " + fileName + ", " + append);
reset();
FileOutputStream ostream = null;
try {
ostream = new FileOutputStream(fileName, append);
} catch (FileNotFoundException ex) {
String parentName = new File(fileName).getParent();
if (parentName != null) {
File parentDir = new File(parentName);
if (!parentDir.exists() && parentDir.mkdirs()) {
ostream = new FileOutputStream(fileName, append);
} else {
throw ex;
}
} else {
throw ex;
}
}
Writer fw = createWriter(ostream);
if (bufferedIO) {
fw = new BufferedWriter(fw, bufferSize);
}
}
首先注意调用方法传入的参数值,分别是fileName,append,bufferedIO和bufferSize。怎么样,这4个属性名字都很熟悉吧。首先,根据fileName和append创建一个FileOutputStream;如果文件不存在则创建这个文件,再把这个文件包装成FileOutputStream;紧接着调用WriterAppender的createWriter方法,把FileOutputStream包装成一个Writer(这个Writer就设置好了encoding了);接着,如果需要缓存,则再把Writer用BufferedWriter包装一次,即可使用。
同样,这个类也没有再去实现append方法或者subAppend方法,而直接是使用的WriterAppender去执行的。其实ConsoleAppender和FileAppender都只是对用什么东西来写做了规定,其他的比如怎么写,就都是WriterAppender来规范的。
代码先看到这里,我们来思考一个问题,我们已经看了FileAppender的实现方式,那么,我们自己来猜想一下RollingFileAppender的实现呢?那下面我们就来简单猜想一下,请大家注意一下,可能我们下面的思路不一定就是RollingFileAppender的实现方式,但是我们先去猜,再看代码,自己的提升会很大。
首先我们考虑,RollingFileAppender应该不需要去覆写activateOption方法,因为RollingFileAppender的初始化动作和FileAppender是相同的,都是根据情况创建好日志文件而已。但是,在RollingFileAppender中,就必须要去修改subAppend方法了。因为在日志记录的过程中,涉及到文件备份/新开操作和当文件索引号达到规定最大号码的时候,删除备份文件的动作。这些都只有在subAppend方法中实现。首先,当记录完成一条日志后,就需要去判断当前日志文件的大小,如果文件大小满足了最大文件上限,就需要执行文件处理动作。首先把当前日志文件之前的所有日志文件备份名+1,即log.log.N变成log.log.N+1,然后再帮当前日志文件重命名为log.log.1,如果当前的备份日志文件名称数量已经超过了maxBackupIndex大小,则直接删除log.log.maxBackupIndex+1这个文件即可,所以,在RollingFileAppender中,又应该保持一个属性来记录当前已经备份了多少文件号,处理完备份文件之后,再重新创建log.log,并把FileOutputStream重新定向到这个新的日志文件即可。
那么真正的代码是否如我们所设想呢?这个大家就下去自己看看RollingFileAppender的实现,保证能学到别人更好的设计思想和规范的编码方式。