Mybatis源码之路

Log 日志

2020-09-14  本文已影响0人  93张先生

三方日志的初始化

运行时决定第三方日志

LogFactory 类加载时会运行静态代码块,以此尝试 Slf4j、JakartaCommonsLogging、Log4j2、Log4j、Jdk14Log、NoLog 等多种第三方日志,直到 logConstructor 不为 Null,此时就确定了使用哪一种第三方日志。

  static {
    // 函数式接口,执行顺序 tryImplementation,然后执行 函数表达式,再执行函数表达式中的 setImplementation()
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

/**
   * 如果 logConstructor == null,就不会再执行 run(),
   * 在 本类上面 static 代码块,会多次调用这个方法,根据调用顺序优先级,只会初始化一个 logConstructor,logConstructor 只会被一个三方日志中的一个初始化。
   * @param runnable 作为 java8 的新特性,函数式接口;和 Thread 没有半毛钱关系;
   *
   */
  private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        // run() 函数式接口中方法的执行,比如这个 LogFactory::useSlf4jLogging lambda 表达式
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

  private static void setImplementation(Class<? extends Log> implClass) {
    try {
      //  三方日志的带有 String 参数的构造器,通过构造器创建 log 实例,并 赋值给 logConstructor
      // 获取 String 类型参数 的构造器;因为 Log4j2Impl 等具体第三方构造器的 参数是 String 类型
      Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
      // LogFactory.class.getName() 仅作为 log 具体实现类中构造函数的参数;
      // 构造器参数
      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);
    }
  }

自定义配置 Log 类

  1. mybatis-config.xml 配置第三方 Log
  2. 解析 setting 配置
  3. 解析配置的 logImpl 三方日志
    Configuration 调用 LogFactory.useCustomLogging(this.logImpl); 覆盖 初始化时的第三方 Log 日志
// 1. mybatis-config.xml 配置第三方 Log
<setting name="logImpl" value="LOG4J"/>
// 2. 解析 setting 配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 3. 解析配置的 logImpl 三方日志
loadCustomLogImpl(settings);

/**
   * 加载 <setting> 配置的 log 日志实现
   * @param props
   */
  private void loadCustomLogImpl(Properties props) {
    // logImpl 全路径名称
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
  }

// 4. Configuration 调用 LogFactory.useCustomLogging(this.logImpl); 覆盖 初始化时的第三方 Log 日志
//  设置 <setting> 中 第三方 log  的日志实现
  public void setLogImpl(Class<? extends Log> logImpl) {
    if (logImpl != null) {
      this.logImpl = logImpl;
      LogFactory.useCustomLogging(this.logImpl);
    }
  }

  // 加载 <setting> 配置的第三方日志
  public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
    setImplementation(clazz);
  }

Log 对象的获取

在 LogFactory 工厂类,创建具体的 Log 对象。
根据初始化的 logConstructor 对象实例,获取具体的 Log 对象,用来记录日志

  /**
   * 样例:private static final Log log = LogFactory.getLog(BaseExecutor.class);
   *
   * 获取的 Log 对象实例的构造方法的参数包含类名称,所以有了 aClass 这个参数
   * 这个参数用来记录,哪个类,使用了 log 对象
   * 返回 log 对象
   * @param aClass 使用 log 对象的类名称
   * @return
   */
  public static Log getLog(Class<?> aClass) {
    // 类名称
    return getLog(aClass.getName());
  }

  /**
   * 根据一个类名称,返回一个 Log 对象
   * logConstructor 是项目初始化的时候,进行了创建赋值
   * @param logger 构造器的参数,要记录日志的类的名称
   * @return
   */
  public static Log getLog(String logger) {
    try {
      // logger 是 构造器参数
      return logConstructor.newInstance(logger);
    } catch (Throwable t) {
      throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
    }
  }

日志适配器

第三方日志组件都有各自的 Log 级别,且都有所不同, Log4j2 则提供了 trace debug info warn error fatal 这六种日志级别。 MyBatis 提供了 trace debug warn error 四个级别。

三方日志 UML 图

image.png

LogFactory

LogFactory 工厂类负责创建对应的日志组件适配器 。
此类 第一步:创建 logConstructor 对象实例,然后根据 logConstructor 对象实例获取具体的 Log 对象,用来记录日志。

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; // 记录当前使用的第三方日志组件所对应的适配器的构造方法
  // 以此从上之下,尝试初始化第三方日志模块,直到 logConstructor != null
  static {
    // 函数式接口,执行顺序 tryImplementation,然后执行 函数表达式,再执行函数表达式中的 setImplementation()
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

  private LogFactory() {
    // disable construction
  }

  /**
   * 样例:private static final Log log = LogFactory.getLog(BaseExecutor.class);
   *
   * 获取的 Log 对象实例的构造方法的参数包含类名称,所以有了 aClass 这个参数
   * 这个参数用来记录,哪个类,使用了 log 对象
   * 返回 log 对象
   * @param aClass 使用 log 对象的类名称
   * @return
   */
  public static Log getLog(Class<?> aClass) {
    // 类名称
    return getLog(aClass.getName());
  }

  /**
   * 根据一个类名称,返回一个 Log 对象
   * logConstructor 是项目初始化的时候,进行了创建赋值
   * @param logger 构造器的参数,要记录日志的类的名称
   * @return
   */
  public static Log getLog(String logger) {
    try {
      // logger 是 构造器参数
      return logConstructor.newInstance(logger);
    } catch (Throwable t) {
      throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
    }
  }
  // 加载 <setting> 配置的第三方日志
  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);
  }

  /**
   * 如果 logConstructor == null,就不会再执行 run(),
   * 在 本类上面 static 代码块,会多次调用这个方法,根据调用顺序优先级,只会初始化一个 logConstructor,logConstructor 只会被一个三方日志中的一个初始化。
   * @param runnable 作为 java8 的新特性,函数式接口;和 Thread 没有半毛钱关系;
   *
   */
  private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        // run() 函数式接口中方法的执行,比如这个 LogFactory::useSlf4jLogging lambda 表达式
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

  private static void setImplementation(Class<? extends Log> implClass) {
    try {
      //  三方日志的带有 String 参数的构造器,通过构造器创建 log 实例,并 赋值给 logConstructor
      // 获取 String 类型参数 的构造器;因为 Log4j2Impl 等具体第三方构造器的 参数是 String 类型
      Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
      // LogFactory.class.getName() 仅作为 log 具体实现类中构造函数的参数;
      // 构造器参数
      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);
    }
  }

}

Jdk14LoggingImpl 适配器

Jdk14LoggingImpl 实现了 org.apache.ibatis logging.Log 接口,并封装了 java.util.logging.Logger对象, org.apche.ibatis.logging.Log 接口的功能全部通过调用 java.util.logging.Logger 对象现, 这就是前面介绍的适配器模式。

public class Jdk14LoggingImpl implements Log {

  // 底层封装的 java.util.logging.Logger 对象
  private final Logger log;

  // 通过 logConstructor.newInstance(logger) 初始化 java.util.logging.Logger 对象; logger 为调用 log 日志的类名称
  public Jdk14LoggingImpl(String clazz) {
    log = Logger.getLogger(clazz);
  }

  @Override
  public boolean isDebugEnabled() {
    return log.isLoggable(Level.FINE);
  }

  @Override
  public boolean isTraceEnabled() {
    return log.isLoggable(Level.FINER);
  }

  @Override
  public void error(String s, Throwable e) {
    log.log(Level.SEVERE, s, e);
  }

  @Override
  public void error(String s) {
    log.log(Level.SEVERE, s);
  }

  @Override
  public void debug(String s) {
    log.log(Level.FINE, s);
  }

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

  @Override
  public void warn(String s) {
    log.log(Level.WARNING, s);
  }

}


上一篇 下一篇

猜你喜欢

热点阅读