Mybatis内部提供了日志模块,用于记录框架内部异常与调试信息;日志实现使用了经典的适配器模式,mybatis自己本身不提供日志功能,而是依赖其他的日志框架来实现;内部支持的优先级是:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
由于各个厂商提供的日志api,日志级别等都不尽相同,所以mybatis对所有支持的日志框架做了封装,自定义了日志打印级别:
error (错误) > warn (警告) > debug(调试信息)>trace
mybatis 里定了了统一的日志接口,Log接口并定义了一系列的接口(目标接口)
/*** @author Clinton Begin*/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);}
提供的实现类(适配器):
日志集成核心类 org.apache.ibatis.logging.LogFactory
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 {// 在LogFactory的静态方法里按照特定的顺序,寻找并加载对于的第三方日志组件,tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}private LogFactory() {// disable construction}/** 获取Log类对象。这个是为了实例化一个Log对象*/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);}}/** tryImplementation 方法里会先判断是否已经加载了日志组件,如果已经加载了则不会继续处理;* 并且这个方法做了异常的吞没,如果没有对于的日志组件也不会影响下一个组件的加载流程;*/private static void tryImplementation(Runnable runnable) {if (logConstructor == null) {try {runnable.run();} catch (Throwable t) {// ignore}}}public static synchronized void useSlf4jLogging() {setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);}/** 这个方法主要功能是去加载提供的实现类,这些实现类里引用了第三方的类,* 如果项目中没有提供对于的jar包则该方法会抛错,然后异常会传递到 tryImplementation方法;* 如果实例化特制类成功,则会将对于的类构造器赋值给 logConstructor,以便后续实例化*/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);}}}
下面以 org.apache.ibatis.logging.log4j.Log4jImpl 为例做分析
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);}@Overridepublic boolean isDebugEnabled() {return log.isDebugEnabled();}@Overridepublic boolean isTraceEnabled() {return log.isTraceEnabled();}@Overridepublic void error(String s, Throwable e) {log.log(FQCN, Level.ERROR, s, e);}@Overridepublic void error(String s) {log.log(FQCN, Level.ERROR, s, null);}@Overridepublic void debug(String s) {log.log(FQCN, Level.DEBUG, s, null);}@Overridepublic void trace(String s) {log.log(FQCN, Level.TRACE, s, null);}@Overridepublic void warn(String s) {log.log(FQCN, Level.WARN, s, null);}}
这个类适配的 org.apache.log4j日志组件,在类里有一个成员变量。相当于适配器模式里面对象的适配器模式。Log4jImpl这个类相当于是将log4j的功能翻译成了Mybatis的功能,做了api的适配
拓展:Mybatis是如何做到日志可以自定义扩展的呢?
从上面的源码分析我们已经看出了,Mybatis内置了
- SLF4J
- Apache Commons Logging
- Log4j2
- Log4j2
- JDK Logging
几种开源的实现作为日志扩展。默认使用的是 “Slf4j ” 如果我们需要自己指定也是可以的,我们只需要在配置文件中加上:
<configuration><settings><setting name="logImpl" value="SLF4J"/></settings></configuration>
mybatis的启动加载的源码中会自动的加载,解析配置文件,
private void parseConfiguration(XNode root) {try {propertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);// 4、根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2loadCustomLogImpl(settings);...}}/*** 在这里我们可以指定日志的实现方式* @param props mybatis-config.xml中setting的properties的配置文件中logImpl实现方式*/private void loadCustomLogImpl(Properties props) {// 根据配置文件中的配置的日志的名称,解析出使用日志的实现类Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));// 方法设置为我们自定义的类,跟踪此方法,找到最后的实现为configuration.setLogImpl(logImpl);}public void setLogImpl(Class<? extends Log> logImpl) {if (logImpl != null) {this.logImpl = logImpl;// 查找字符串参数的构造器LogFactory.useCustomLogging(this.logImpl);}}// 最后到了最关键的 LogFactory# useCustomLogging方法,这个方法如果在加载配置文件阶段就指定了// 那么我们自定义的日志实现类就会生效public static synchronized void useCustomLogging(Class<? extends Log> clazz) {setImplementation(clazz);}
