一 设计类结构
第一部分说了两件事儿:
- 如何根据配置文件分析有用的信息以便对底层结构进行挖掘
- 如何查看源码的类结构以便调研需求完成后续开发设计
在了解我们的终极目标之后,不忙写代码,之前的工作仅仅是调研,为的是确认需求能否实现,以及实现过程中可能触及的技术风险。接下来我们需要根据需求及之前所做的调研,对我们即将要开发的功能做一个初步设计,将实现的框架搭建起来。
我习惯先定义一个类,比如说LogUtil.java:
package com.bubbling;
/**
* 1.实现代码方式配置Log4j <br>
* 2.实现线程级日志对象管理 <br>
* 3.实现日志的异步输出模式 <br>
* 4.实现按日志文件大小及日期进行文件备份
*
* @author 胡楠
*
*/
public class LogUtil
{
}
这个类将作为一个日志工具类使用,对外提供相关方法供调用者进行日志属性配置及日志内容输出。
二 成员设计
接下来是成员设计,成员设计可以说是最重要的,它直接影响类的整体设计思路。先说日志配置中有哪些常见的、使用率高的、必要的属性:
- 文件名
- 文件存储路径
- 单一文件最大存储容量
- 备份文件最大保留数量
- 是否开启异步模式
- 日志输出目的地:文件/控制台/其他
因LogUtil是一个工具类,其成员设计初步拟定为类级成员,作为可被外部配置的公有信息,初步的成员设计如下:
public class LogUtil
{
private static String fileName;
private static String filePath;
private static String fileSize;
private static int maxBackupIndex;
private static boolean isAsynchronous;
private static int logTarget;
public static void setFileName(String value)
{
fileName = value;
}
public static String getFileName()
{
return fileName;
}
……
}
因为需要明确日志输出的目的地,那么我们需要对目的地的定义进行规范设计,像这种对同一类型且仅区分内容的属性设计,通常使用静态不可变成员,或者枚举类型来定义,这里我使用枚举类型来定义它,这样会使得封装性更强:
public class LogUtil
{
private static String fileName;
private static String filePath;
private static String fileSize;
private static int maxBackupIndex;
private static boolean isAsynchronous;
private static LogTarget logTarget;
public enum LogTarget
{
Console, File, Socket
}
public static void setFileName(String value)
{
fileName = value;
}
public static String getFileName()
{
return fileName;
}
……
}
三 方法设计
再来就是方法设计,它体现了一个类的内在或外向的行为方式,是与外界进行交互的窗口。按需求来说,我们需要一个方法给我们返回一个Logger对象,并使用Logger对象进行日志输出,需要明确的是,返回的Logger其属性配置因该从LogUtil的类级成员处取得,并且由方法参数来确定日志的输出目标,不同输出目标的Logger其属性配置亦不相同:
public static Logger getLogger(LogTarget target)
{
Logger logger = null;
if (LogTarget.Console == target)
{
logger = getConsoleLogger();
}
else if (LogTarget.File == target)
{
logger = getFileLogger();
}
else if (LogTarget.Socket == target)
{
logger = getSocketLogger();
}
return logger;
}
private static Logger getSocketLogger()
{
// TODO Auto-generated method stub
return null;
}
……
四 实现Logger对象实例化方法
已经定义好了大体框架,接下来就是实现各种get***Logger()方法了,这里以输出到文件使用RollingFileAppender为例,做一个简单的实现样例,其他Logger对象的实现大同小异,仅在属性上有所区别:
public static Logger getLogger(LogTarget target, String loggerName)
{
Logger logger = null;
if (LogTarget.Console == target)
{
logger = getConsoleLogger(loggerName);
}
else if (LogTarget.File == target)
{
logger = getFileLogger(loggerName);
}
else if (LogTarget.Socket == target)
{
logger = getSocketLogger(loggerName);
}
return logger;
}
private static Logger getSocketLogger(String loggerName)
{
// TODO Auto-generated method stub
return null;
}
private static Logger getFileLogger(String loggerName)
{
// 初始化一个RollingFileAppender对象
RollingFileAppender appender = new RollingFileAppender();
// 设置日志内容追加到文件内容末尾
appender.setAppend(true);
// 设置日志文件的存储位置
appender.setFile(getFilePath() + File.separator + getFileName());
// 不开启异步模式
appender.setBufferedIO(false);
// 仅开启异步模式,缓存大小才有意义
appender.setBufferSize(0);
// 下面的方法是对上面四个属性设置的一个封装
// appender.setFile("", true, false, 0);
// 需要激活Appender对象的配置,这样属性设置才会生效
appender.activateOptions();
// 注意这里需要是指Logger对象名,后续设计会对此处进行重构,目前以调用类的SimpleName作为Logger对象的name属性值
Logger logger = Logger.getLogger(loggerName);
// 为Logger对象添加Appender成员
logger.addAppender(appender);
// 设置Logger对象不继承上层节点属性配置,仅向文件中输出内容
logger.setAdditivity(false);
// 为什么设置日志输出级别为Trace,因为后续我们需要通过LogUtil公开的方法对日志级别进行动态控制,所以此处暂时设置为最低级别
logger.setLevel(Level.TRACE);
return logger;
}
……
此时已经算是成功了一半了,应用程序通过LogUtil的getLogger()方法会得到一个可用的Logger对象,调用Logger对象提供的日志输出方法即可实现日志的输出。
那么有人问我,我是怎么知道Logger、Appender有那些属性需要设置的?答:官方文档/源码查阅。这里我不贴官方文档了,用Eclipse给大家看看我是怎么知道这些属性设置方法的:
如果需要查看更详细的说明,跟到源码的方法定义处,看看人家设计师当初是怎么对方法进行设计的即可,这依然是通用的、常规的操作(看源码很牛逼?瞎说八道)。
最后再总结下,目前尚未完成的设计:
- 线程级日志对象控制
- 按处理线程动态设置日志输出界别
- 日志的异步输出模式
这些内容会在后续的博客里逐步介绍。可能会有人觉得我很傻逼,写的罗里吧嗦,但请理解我的初衷,我只是想分享我当时做设计的时候,是如何一步步完成需求的,或许对于高手而言不值一提,但我更希望这里的一些思路能够对某些朋友有所帮助。