mybatis源码(五)mybatis日志实现
日志在项目中很常见,能够记录系统的运行状态,有助于开发人员排查系统bug,我们使用什么样的日志,在maven的pom中添加相应的依赖,就可以使用了,那么mybatis是怎么做的呢
mybatis通过Log接口定义了日志规范
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); }
mybatis针对不同的日志框架提供了不同的实现
实现类中的代码都比较简单,都是调用对应日志框架的api来打印日志。例如:
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); } }
mybatis支持7中不同的日志策略。在LogFactory中,如下所示
// 自定义日志实现 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); } // JCL框架输出日志 public static synchronized void useCommonsLogging() { setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class); } // Log4j框架输出日志 public static synchronized void useLog4JLogging() { setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class); } // Log4j2框架输出日志 public static synchronized void useLog4J2Logging() { setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class); } // JUL框架输出日志 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); }
setImplementation 方法指定日志实现类
private static void setImplementation(Class<? extends Log> implClass) { try { // 获取日志实现类的Constructor对象 Constructor<? extends Log> candidate = implClass.getConstructor(String.class); // 根据日志实现类创建Log实例 Log log = candidate.newInstance(LogFactory.class.getName()); if (log.isDebugEnabled()) { log.debug("Logging initialized using '" + implClass + "' adapter."); } // 记录当前使用的日志实现类的Constructor对象 logConstructor = candidate; } catch (Throwable t) { throw new LogException("Error setting Log implementation. Cause: " + t, t); } }
使用mybatis时,我们如果不指定使用哪种日志框架,mybatis能够按照顺序从classpath下查找日志框架的jar包。如果classpath下面有对应日志的jar包,则使用该框架打印日志
mybatis动态查找日志框架的代码是在LogFactory中的static代码块中完成的
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(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 } } }
static类加载的时候,首先加载 useSlf4jLogging()方法。表示使用SLF4J框架。然后从classpath中查找相关的jar包,如果classpath下不存在相关的jar包,则useSlf4jLogging()方法就会抛出异常ClassNotFoundException等异常。这个异常会被catch捕获。捕获完成后,并不做任何处理继续扫描其他的类型的日志框架。继续寻找下一个日志框架的过程中,如果存在相应的日志jar包,则使用对应的日志框架类输出日志。如果日志框架中不存在任何框架的日志包,则使用NoLoggintImpl()的实现类。扫描的顺序为SLF4J--->JCL--->Log4j2--->Log4j--->JUL--->NoLogging
mybatis支持在mybatis-config主配置文件中,指定使用哪种框架
<configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
属性值支持使用别名的方式,这是因为在Configuration的构造方法中,添加了别名的注册
// 日志框架的别名 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
mybatis中的Configuration对象中,还维护了如下代码
protected Class<? extends Log> logImpl; public Class<? extends Log> getLogImpl() { return logImpl; } public void setLogImpl(Class<? extends Log> logImpl) { if (logImpl != null) { this.logImpl = logImpl; // 调用LogFactory类的useCustomLogging()方法指定日志实现类 LogFactory.useCustomLogging(this.logImpl); } }
在解析主配置文件的时候,是通过如上代码指定日志框架
在Configuration对象中,通过setLogImpl方法调用 LogFactory.useCustomLogging(this.logImpl); 来指定日志实现类的。而mybaits中所有的日志实例是通过LogFactory创建的。这就保证了整个日志的输出框架使用的同一种日志