前言:
java开发中常用的日志框架有Log4j,Log4j2,Apache Commons Log,java.util.logging,slf4j等,这些工具对外的接口不尽相同。为了统一这些工具的接口,Mybatis定义了一套统一的日志接口供上层调用,并为上述日志框架提供了相应的适配器。
在Mybatis的日志模块中,使用了适配器模式。Mybatis调用其他日志模块时,使用了其内部接口(org.apache.ibatis.logging.Log接口)。但是第三方日志组件对外提供的接口各不相同,Mybatis为了集成和服用这些第三方日志组件,在其模块中提供了多种Adapter,将这些第三方日志组件对外接口适配成了org.apache.ibatis.logging.Log接口,这样Mybatis内部就可以统一通过org.apache.ibatis.logging.Log接口调用第三方日志组件的功能了。
日志适配器:
Mybatis的日志模块位于org.apache.ibatis.logging包中,改模块中的Log接口定义了日志模块的功能,当然日志适配器也会实现此接口。LogFactory工厂类负责创建对应的日志组件适配器。
LogFactory类解析:
/** * 在LogFactory类加载时会执行其静态代码块,其逻辑是按序加载并实例化对应的日志适配器, * 然后使用LogConstructor这个静态字段,记录当前使用的第三方日志组件适配器 */ 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; /** * 对每种日志组件调用tryImplementation()进行尝试加载,具体的调用顺序是: * useSlf4jLogging——>useCommonsLogging——>useLog4J2Logging * ——>useLog4JLogging——>useJdkLogging——>useNoLogging */ 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 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); } /** * 使用Slf4j日志组件 */ 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); } /** * 此方法会先检测logConstructor,若为空则调用runnable.run()方法 */ 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字段 logConstructor = candidate; } catch (Throwable t) { throw new LogException("Error setting Log implementation. Cause: " + t, t); } } }
JDBC调试:
在Mybatis的日志模块中有一个jdbc包,它并不是将日志信息通过jdbc存入数据库,而是通过JDK动态代理的方式,将JDBC操作通过制定的日志框架打印出来。这个功能通常在开发阶段使用,它可以输出sql语句、用户传入的参数、sql影响的行数等信息。对调试程序非常有用。
BaseJdbcLogger是一个抽象类,它是jdbc包下其他Logger类的父类,具体作用请看源码分析,此处不贴源码: