跳到主要内容
  1. 所有文章/
  2. 《Mybatis3源码深度解析》笔记/

Mybatis源码-日志实现

·📄 2512 字·🍵 6 分钟

Java日志框架 #

  • Log4j:Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gulcu首创的,现在则是Apache软件基金会的一个项目。
  • Log4j2:ApacheLog4j 2是Apache开发的一款Log4j的升级产品。
  • Commons Logging:Apache 基金会所属的项目,是一套Java 日志接口,之前叫 JakartaCommons Logging,后更名为 Commons Logging。
  • SLF4J:全称为SimpleLoggingFacadeforJava,类似于CommonsLogging,是一套简易Java日志门面,本身并无日志的实现。
  • Logback:是一套日志组件的实现,属于SLF4J阵营。JUL:全称是JavaUtil Logging,是JDK1.4以后提供的日志实现。

发展历史 #

1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(TracingAPI)。经过不断完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。

后来Log4j成为Apache基金会项目中的一员。期间Log4j近乎成了 Java社区的日志标准。据说Apache基金会还曾经建议 Sun引入Log4j到Java的标准库中,但被 Sun拒绝了。

2002年,Java 1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),它的实现基本模仿了Log4j的实现。在JUL问世以前,Log4j就已经成为一项成熟的技术,这使得Log4j在选择上占据了一定的优势。

接着,Apache推出了JCL(Jakarta Commons Logging),它只是定义了一套日志接口(其内部也提供一个SimpleLog的简单实现),支持运行时动态加载日志组件的实现。也就是说,在应用程序代码中,只需调用CommonsLogging的接口,底层实现可以是Log4j,也可以是JUL。

后来(2006年),Log4j的作者不适应Apache的工作方式,离开了Apache。然后先后创建了SLF4J和Logback两个项目并回瑞典创建了QOS公司。

SLF4J类似于CommonsLogging,属于日志门面,而Logback是对SLF4J日志门面的实现,QOS官网上是这样描述Logback的:TheGeneric,ReliableFast&FlexibleLoggingFramework(一个通用、可靠、快速且灵活的日志框架)。

现今,Java日志领域被划分为两大阵营:CommonsLogging阵营和SLF4J阵营

CommonsLogging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底,有人分析了GitHub上的30000个项目,统计出了流行的100个Libraries,从图8-1中可以看出SLF4J的发展趋势更好。Apache眼看有被Logback反超的势头,于2012年重写了Log4j 1.x,成立了新的项目Log4j 2。Log4j2具有logback的所有特性。

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);
}
  • Apache Commons Logging:使用JCL 输出日志
  • Log4j2:使用Log4j2框架输入日志
  • JavaUtil Logging:使用JDK内置的日志模块输出日志。
  • Log4j:使用Log4j框架输出日志
  • No Logging:不输出任何日志。
  • SLF4J:使用SLF4J日志门面输出日志
  • Stdout:将日志输出到标准输出设备(例如控制台)。

日志实现也比较简单,输出到对应的设备即可,以StdOutImpl 为例

public class StdOutImpl implements Log {

  public StdOutImpl(String clazz) {
    // Do Nothing
  }

  @Override
  public boolean isDebugEnabled() {
    return true;
  }

  @Override
  public boolean isTraceEnabled() {
    return true;
  }

  @Override
  public void error(String s, Throwable e) {
    System.err.println(s);
    e.printStackTrace(System.err);
  }

  @Override
  public void error(String s) {
    System.err.println(s);
  }

  @Override
  public void debug(String s) {
    System.out.println(s);
  }

  @Override
  public void trace(String s) {
    System.out.println(s);
  }

  @Override
  public void warn(String s) {
    System.out.println(s);
  }
}

Logger对象由LoggerFactory 创建:

public class LoggerFactory {  
  public static Logger getLogger(Class<?> aClass) {  
    return new Logger(LogFactory.getLog(aClass));  
  }  
  public static Logger getLogger(String logger) {  
    return new Logger(LogFactory.getLog(logger));  
  }
}

// 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 {
  // 这里的Runnable接口和多线程没有关系,因为没有Thread.start()
    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);
  }

  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);
  }

  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对象
      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);
    }
  }

}

如上面的代码所示,在setImplementation()方法中,首先获取MyBatis日志实现类对应的Constructor对象,然后通过LogFactory类的logConstructor属性记录当前日志实现类的Constructor对象。所以当我们调用LogFactory类的useLog4JLogging()方法时,就确定了使用org.apache.ibatis.logging.log4j.Log4jImpl实现类输出日志,而Log4jImpl实现类又将日志输出操作委托给Log4j框架,这样就确定了使用Log4j框架输出日志。

MyBatis日志模块设计得比较巧妙的一点是当我们未指定使用哪种日志实现时,MyBatis能够按照顺序查找Classpath下的日志框架相关JAR包。如果Classpath 下有对应的日志包,则使用该日志框架打印日志。

配置文件中可以指定日志实现类:

<settings>
  // LOG4J是日志别名
  <setting name="logImpl"value="LOG4J"/>
</settings>

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",Jdkl4LoggingImpl.class);
typeAliasRegistry.registerAlias("STDoUT_LOGGING",StdOutImpl.class);
typeAliasRegistry.registerAlias("No_LOGGING", NoLoggingImpl.class);

当MyBatis 框架启动时,解析主配置文件中的 loglmpl 参数,然后调用Configuration 类的setLogImpl()方法设置日志实现类。setLogImpl()方法内容如下: