一、模块简介
MyBatis的日志模块位于org.apache.ibatis.logging包中,通过适配器模式,实现了集成和复用常见的第三方日志组件。Mybatis支持的多个第三方日志插件,优先级由低到高为slf4J、commonsLoging、Log4J2、Log4J和JdkLog等。
二、适配器模式
1、概念
这里简单对适配器模式进行一个解释。适配器模式的主要目的是解决由于接口不能兼容而导致类无法使用的问题,适配器模式会将需要适配的类转换成调用者能够使用的目标接口。适配器模式中涉及的几个角色,如下所述:
- 目标接口( Target ):调用者能够直接使用的接口。
- 需要适配的类( Adaptee ): 一般情况下, A daptee 类中有真正的业务逻辑,但是其接口不能被调用者直接使用。
- 适配器( Adapter): Adapter 实现了Target 接口,并包装了一个Adaptee 对象。Adapter在实现Target 接口中的方法时,会将调用委托给Adaptee 对象的相关方法,由Adaptee完成具体的业务。
2、适配器模式类图
三、包结构
日志模块的包结构,如下图所示。主要实现了对slf4J、commonsLoging、Log4J2、Log4J和JdkLog等第三方日志框架进行集成。其中jdbc子包下主要是提供了一系列对JDBC操作过程中使用到的对象进行了封装代理,增加其日志功能。
四、Log接口
日志接口,定义了trace、debug、warn、error四个日志级别,这基本与主流日志框架的日志级别类似,可以满足绝大多数场景的日志需求。
package org.apache.ibatis.logging;
/**
* 日志接口
* @author Clinton Begin
*/
public interface Log {
/**
* debug是否启用
* @return
*/
boolean isDebugEnabled();
/**
* trace是否启用
* @return
*/
boolean isTraceEnabled();
/**
* error级日志打印
* @param s 日志消息
* @param e errors Or exceptions的堆栈消息
*/
void error(String s, Throwable e);
/**
* error级日志打印
* @param s 日志消息
*/
void error(String s);
/**
* debug级日志打印
* @param s 日志消息
*/
void debug(String s);
/**
* trace级日志打印
* @param s 日志消息
*/
void trace(String s);
/**
* warn级日志打印
* @param s 日志消息
*/
void warn(String s);
}
五、LogFactory类
LogFactory 工厂类负责创建对应的日志组件适配器。
- 字段
/**
* Marker to be used by logging implementations that support markers
*/
public static final String MARKER = "MYBATIS";
/**
* 记录当前使用的第三方日志组件所对应的适配器的构造方法
*/
private static Constructor<? extends Log> logConstructor;
- 静态代码块
静态代码块通过执行trylmplementation()方法依次尝试获取SLF4J、Apache Commons Logging、Log4j2、Log4j、JDK logging类型的日志实现。trylmplementation()方法首先会检测logConstructor字段, 若为空则调用Runnable.run(),其中会调用use*Logging()方法,尝试获取对应的Log实现。
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();
}
});
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
- use*Logging()方法
use*Logging()方法,尝试获取对应的Log日志实现。其中,通过setImplementation()方法获取对应日志实现类的构造器,并对字段logConstructor进行初始化。下面以useLog4J2Logging()方法为例,来介绍代码:
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
六、日志适配器类
在LogFactory类中,通过setImplementation()方法实现字段logConstructor的初始化。其中该方法的参数就是指定日志适配器的完全限定名,通过反射机制,获取对应的构造函数。下面以log4j2和log4j为例,介绍其中的用法。
其中,Log4jImpl 就是对接口Log的实现。log4j2的实现稍微有些差别,它对应的适配器类是Log4j2Impl,其中根据获取Logger实例对象的类进行判断,如果是AbstractLogger类型,就通过Log4j2AbstractLoggerImpl来实例化Log对象,否则使用Log4j2LoggerImpl实例化对象。和log4j2用法一样的适配器,还有slf4j,其他的都和Log4j使用方式一样。
public class Log4jImpl implements Log {
private static final String FQCN = Log4jImpl.class.getName();
private final Logger log;
public Log4jImpl(String clazz) {
log = Logger.getLogger(clazz);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.log(FQCN, Level.ERROR, s, e);
}
@Override
public void error(String s) {
log.log(FQCN, Level.ERROR, s, null);
}
@Override
public void debug(String s) {
log.log(FQCN, Level.DEBUG, s, null);
}
@Override
public void trace(String s) {
log.log(FQCN, Level.TRACE, s, null);
}
@Override
public void warn(String s) {
log.log(FQCN, Level.WARN, s, null);
}
}
public class Log4j2Impl implements Log {
private final Log log;
public Log4j2Impl(String clazz) {
Logger logger = LogManager.getLogger(clazz);
if (logger instanceof AbstractLogger) {
log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
} else {
log = new Log4j2LoggerImpl(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);
}
}