mybatis框架源码的实现相对spring来说要简单的很多,模块的分工也很明确,每个模块的代码量也不是很大,比较容易阅读,如果你对设计模式很了解的话。里面用到很多设计模式,如工厂模式、代理模式、装饰器模式、适配器模式等等,都是值得平时开发学习和借鉴的,都说高手的代码都是向高级框架靠拢,谁知道他是自己设计还是看了源码学习的呢,对不?
mybatis的模块比较多,如对外的用SqlSession、内部底层日志logging、数据源模块、反射等,这一篇来看mybatis源码中最简单的部分logging日志。这个模块主要就是用工厂模式生成适配不同logging组件的日志类。然后就是适配器模式,适配不同的logging组件,并支持无侵入的logging组件扩展。
1. logging整体了解
想了解细节,先了解整体,会更好。看图:
市场上日志组件有很多,各个公司采用的日志组件不尽相同,但是可能都要使用到mybatis,如果mybatis只能支持一种日志的话,那么就会出现其他日志组件无法打印日志的问题。
此时使用适配器模式,不论其他的组件是什么样的,统一使用适配类去对这些组件进行封装,适配类都实现Log接口,将Log暴露给mybatis调用方,调用方直接调用log接口内的方法即可。不用管底层到底是适配哪个日志组件。另外每种日志组件的日志级别分类都是有所差别的,做了统一封装,就不用考虑日志级别变化的问题。
这种设计拓展性很好,如果需要添加日志组件,只要写一个适配类去封装此日志组件,然后在工厂类中添加日志适配类加载的代码就可以,上层的业务代码是无需任何修改,是无感的。
2. 看一下源码
2.1 Log接口类
Log接口类的代码:
public interface Log {
boolean isDebugEnabled();//是否可以debug日志
boolean isTraceEnabled();//是否可以跟进日志
//错误
void error(String s, Throwable e);
//错误
void error(String s);
//调试
void debug(String s);
//跟进
void trace(String s);
//警告
void warn(String s);
}
这里日志接口很简单,就四种日志级别。如果多log4j、commons-logging这些日志组件有所了解的话,可以知道他们的日志级别都是有所不同。因此这里就必须要是做适配了,适配器就上场喽。(slf4j为例)
2.2 Slf4jImpl实现类
public class Slf4jImpl implements Log {
private Log log;
// 构造函数(很重要)
public Slf4jImpl(String clazz) {
// 获取logger实例,这是对应到组件的logger
Logger logger = LoggerFactory.getLogger(clazz);
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
// 创建适配对象
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException e) {
// fail-back to Slf4jLoggerImpl
} catch (NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
log = new Slf4jLoggerImpl(logger);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.error(s, e);
}
@Override
public void error(String s) {
log.error(s);
}
@Override
public void debug(String s) {
log.debug(s);
}
@Override
public void trace(String s) {
log.trace(s);
}
@Override
public void warn(String s) {
log.warn(s);
}
}
这里主要看的就是构造函数,看构造函数是如何创建适配器对象的。先根据实际日志组件类创建logger实例,然后根据对logger实例进行包装,得到一个适配后的log实例,也就是对应日志组件的log适配器对象。Slf4jLocationAwareLoggerImpl
内的代码就不跟进去看了,里面很简单,就是将传入的logger赋值给成员变量。然后在四种日志级别的方法里面都使用这个logger对象进行日志打印。
2.3 LogFactory工厂类
工厂类做了两件事情,第一步在静态代码块中依次扫描日志实现,然后根据优先级加载日志实现类的构造器,只会加载一个,谁在前加载谁。第二步提供getLog
方法,通过此方法中调用构造器的newInstance
方法构造正式的日志对象和日志适配对象。
//加载日志构造器的静态代码块
//根据顺序依次执行日志类的加载,有加载顺序和优先级
//顺序(优先级):slf4j->commons-logging->log4j-logging->log4j->jdk->no-loging
static {
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
}
详细如代码,但是这里要知道的是,加载的日志构造器并不是对应日志组件的日志类构造器,而是适配器的构造器。可以看一下上面适配器的源码构造方法。
//获取日志对象,调用getLog(String logger)方法
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
//获取日志对象
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
创建日志实例的方法如上,很简单,就是newInstance
方法的调用。
3. 总结
日志组件适配的整个过程不难,代码也很简洁,过程也很清晰,然后现在通过下面的图总结一下执行的流程。
日志工厂类加载日志适配器类过程。
通过gotLog
方法触发创建日志适配器类对象和日志组件对象。