Mybatis日志实现分析

538 阅读2分钟

实现目的

提供统一的日志接口操作规范,适配7种主流的日志框架

源码package

src/main/java/org/apache/ibatis/logging

使用场景

在Mybatis配置文件中,使用

<settings>
    ...
    <setting name="LogImpl" value="LOG4J"/>
    ...
</settings>

指定使用LOG4J作为日志框架。 除此之外,从Configuration类的

支持的7中主流日志框架
可以看出,setting节点的value支持7类值,分别对应7种主流的日志框架

适配器模式应用

主要用到了对象适配器模式。针对不同的日志框架提供对Log接口对应的实现。分别支持了

  • Apache Common Logging: 使用JCL输出日志
  • Log4J2
  • Java Util Logging: 使用JDK自带的日志模块
  • Log4j
  • No Logging: 不输出日志
  • SLF4J: 使用SLF4J日志门面模式
  • Stdout: 将日志输出到标准输出设备,eg: 控制台
    Log接口的实现类
    以实现了log4j日志接口Logger到MyBatis日志接口Log的转换类:Log4jImpl为例
  1. Log4jImpl实现了Log接口,实现Log接口的方法。
public class Log4jImpl implements Log {}
  1. 持有Logger对象的引用,并且调用Log接口的方法,实际上是通过调用Logger对象的方法实现的。

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 void error(String s, Throwable e) {
    log.log(FQCN, Level.ERROR, s, e);
  }

  @Override
  public void trace(String s) {
    log.log(FQCN, Level.TRACE, s, null);
  }

  ...
}

工厂模式的应用

在前面提到了Mybatis通过对象适配器模式支持多种不同的日志输出策略,但实际使用哪种方式输出日志,则采用了 工厂模式来创建(LogFactory类)。可以通过Mybatis内置的UT LogFactoryTest跟踪该类的实现方式,如:

class LogFactoryTest {
  @Test
  void shouldUseSlf4j() {
    LogFactory.useSlf4jLogging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), Slf4jImpl.class.getName());
  }
}

通过LogFactory可以创建指定日志框架的实现类,如:

  /**
   * 使用自定义的日志框架输出日志
   *
   * @param clazz 日志实现类
   */
  public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
    setImplementation(clazz);
  }

  /**
   * 指定指定日志实现类
   *
   * @param implClass 日志实现类
   */
  private static void setImplementation(Class<? extends Log> implClass) {
    try {
      // 获取Log实现类的构造器对象
      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框架启动时,解析主配置文件中的LogImpl参数后调用Configuration类的setLogImpl()方法设置日志的实现类:

  /**
   * 设置日志实现类
   *
   * @param logImpl 日志实现类
   */
  public void setLogImpl(Class<? extends Log> logImpl) {
    if (logImpl != null) {
      this.logImpl = logImpl;
      // 使用工厂类创建指定的日志实现类,保证整个Mybatis框架只使用一种指定的日志框架
      LogFactory.useCustomLogging(this.logImpl);
    }
  }

扩展阅读

面向对象编程设计模式------适配器模式