简述
本文主要说明MyBatis的logging模块是如何适配各种日志组件的,以及为什么MyBatis日志组件的优先加载顺序为sf4j -> commons logging -> log4j2 -> log4j -> jdk logging
适配器模式
MyBatis主要通过适配器模式完成各种日志组件的适配工作,适配器模式是23种设计模式的一种,这里只会做简要的说明,具体去进行学习。
适配器模式主要就是将一个接口转换成客户希望的一个接口,是接口不兼容的类可以一起工作。
- 目标接口:Target,该角色把其他类转换为我们期望的接口
- 被适配者: Adaptee 原有的接口,也是希望被改变的接口
- 适配器: Adapter, 将被适配者和目标接口组合到一起的类
Logging模块结构
Log接口就是适配器模式中的Target角色,而每一个子包中则包含有各个Adapter,他们都继承了Log接口。
Log接口
这里直接贴上Log接口的源码,Log接口的作用,主要就是定义了Adater需要实现的方法,从而来统一在不同的log组件下的异常等级。从Log接口中可以看出Mybatis有trace,warn,debug,error四种等级的日志。
Adater适配器
这里拿commons-logging的适配器来做例子。
JakartaCommonsLoggingImpl实现了Log接口,在对应的实现方法上,其实就是直接调用了Commons-logging组件的方法。
LogFatory
我们已经有了各个日志组件的适配器了,那么在外部去log的时候怎么确定要哪个适配器呢?这就是LogFatory类的作用,先贴上源码
package org.apache.ibatis.logging;
import java.lang.reflect.Constructor;
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public final class LogFactory {
/**
* Marker to be used by logging implementations that support markers.
*/
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> logConstructor;
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
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);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}
public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}
public static synchronized void useStdOutLogging() {
setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}
public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
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);
}
}
}
首先外部会调用LogFatory的getLog方法,该方法如下:
可以发现getLog中直接使用logConstrutor来实例化组件的,这个类变量实在静态代码块中被赋值的。
静态代码中多次调用了tryImplementation方法,调用的顺序,就是Mybatis加载各种Log组件的优先级。
在tryImplementation中,只要logConstructor不为null,就不会再执行后面的方法。因此可以保证使用log组件的优先级。这里以我们引入的是commons logging为例:
通过反射机制,实例化对应的适配器,并赋值给类变量。
至此logging模块的大致流程已经分析完毕。