Mybatis 没有提供日志实现类,需要接入第三方的日志组件,但第三方组件都有自己的log级别,并且各不相同,Mybatis 统一提供了trace debug warn error四个日志级别。Mybatis 使用适配器模式,在每个第三方日志厂商和自己的log 之间都存在一个XXXlog 适配器,将第三方的日志级别适配为自己的log级别。
接下来看一下是如何接入日志的,在LogFactory 中存在一个logConstructor构造器,当实例化LogFactory 时调用静态块,依次执行 setImplementation 方法,去构造对应第三方日志的适配器。自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4j -> commonsLoging -> Log4J2 -> Log4J -> JDKLog
public final class LogFactory {
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 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);
}
}
以 SlfjLoggerImpl 为例查看对应适配器类都做了什么? 其实就是把第三方日志级别和mybatis 中log级别做了一个适配
class Slf4jLoggerImpl implements Log {
private final Logger log;
public Slf4jLoggerImpl(Logger logger) {
log = 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);
}
}
Mybatis 日志的使用优雅的嵌入到主体功能中(动态代理增强),接下来我们看一下是如何进行日志增强的。
在BaseExecutor 中我们看到获取连接方法,其实是使用ConnectionLogger.newInstance获取的所以下面我们就主要看一下ConnectionLogger对象(getConnection 是在调用doQuery执行查询数据库时候调用的)
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
ConnectionLogger 继承BaseJdbcLogger 实现InvocationHandler(使用JDK 动态代理进行增强)
我们来看一下ConnectionLogger的invoke 方法,显示判断了是否是自己的方法,过滤掉。当时prepareStatement 方法时,如果是debug 级别,打印日志,同时创建PreparedStatement 的代理增强对象
@Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
接下来剩下的几个代理对象我们就不一一看了,原理都是一样的,打印日志并生成下一步骤的代理对象,所以说ConnectionLogger 是承上启下的作用。
总结
Mybatis 的日志接入是使用适配器模式,在第三方日志和自己的日志之间使用适配器对象进行了日志级别的适配。日志的增强是使用代理模式(JDK 动态代理进行日志的增强)