一:Mybatis第三方日志组件
我们都知道Mybatis本身是没有提供给日志的实现类,Mybatis的日志是接入了第三方的日志组件并设置了统一的四个级别:trace,warm,debug,error。Mybatis一共接入了5总日志插件并在LogFactory类的静态代码块定义了5种日志的使用顺序。
public final class LogFactory {
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
从上面的代码可以看出日志组件的加载顺序为:Slf4j–>CommonsLogging–>Log4j2–>Log4J–>JdkLog
二:logger的适配器模式
Mybatis日志主要是通过适配器模式来实现。适配器模式主要是作为两个不兼容接口的桥梁,将一个类的接口转换成另一个可供用户使用的接口
接下来以Log4J为例:
Mybatis提供了一个Log的接口作为目标角色,提供了5种事务级别的方法:
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
Log4JImpl作为适配器角色,Log4J的Logger作为适配者角色,可以看到error()方法最终还是会调用Log4j的级别为ERROR的方法。
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);
}
上面提到LogFactory初始化时会执行选择第三方组健的代码块,之后会执行tryImplementation(LogFactory::useLog4JLogging);的方法,最后调用setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class)反射拿到对应的构造方法。这样就可以构造创建Log对象。
//构造方法
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);
}
//获取构造方法
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
//通过newInstance创建对象
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 useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
//Log4j组健传入参数org.apache.ibatis.logging.slf4j.Slf4jImpl.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);
}
}
三:Mybatis日志的调用
看了上面博主写的之后小伙伴肯定明白了Mybatis的Log实现原理,这时候有些小伙伴就要说了,我确实知道Log的原理了但是它是在什么时候调用的呢?接下来就以数据库查询介绍一下日志的调用。
执行器SimpleExecutor的prepareStatement是JDBC的一个入口方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//建立数据库连接
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
接下来我们进入getConnection方法看一下
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
//动态代理,增强Collection
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
@Override
//JDBC的conn.prepareStatement会进入,并返回增强的PreparedStatement
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);
//返回增强的PreparedStatement
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;
类似的PreparedStatement 的动态代理也会返回增强的ResultSet
由此可见JDBC的日志都是通过动态代理对方法增强输出日志信息。