Mybatis源码学习(6)-日志模块之Log、LogFactory及适配器类

导读:本篇文章讲解 Mybatis源码学习(6)-日志模块之Log、LogFactory及适配器类,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、模块简介

  MyBatis的日志模块位于org.apache.ibatis.logging包中,通过适配器模式,实现了集成和复用常见的第三方日志组件。Mybatis支持的多个第三方日志插件,优先级由低到高为slf4J、commonsLoging、Log4J2、Log4J和JdkLog等。

二、适配器模式

1、概念

  这里简单对适配器模式进行一个解释。适配器模式的主要目的是解决由于接口不能兼容而导致类无法使用的问题,适配器模式会将需要适配的类转换成调用者能够使用的目标接口。适配器模式中涉及的几个角色,如下所述:

  • 目标接口( Target ):调用者能够直接使用的接口。
  • 需要适配的类( Adaptee ): 一般情况下, A daptee 类中有真正的业务逻辑,但是其接口不能被调用者直接使用。
  • 适配器( Adapter): Adapter 实现了Target 接口,并包装了一个Adaptee 对象。Adapter在实现Target 接口中的方法时,会将调用委托给Adaptee 对象的相关方法,由Adaptee完成具体的业务。
2、适配器模式类图

在这里插入图片描述

三、包结构

  日志模块的包结构,如下图所示。主要实现了对slf4J、commonsLoging、Log4J2、Log4J和JdkLog等第三方日志框架进行集成。其中jdbc子包下主要是提供了一系列对JDBC操作过程中使用到的对象进行了封装代理,增加其日志功能。
在这里插入图片描述

四、Log接口

  日志接口,定义了trace、debug、warn、error四个日志级别,这基本与主流日志框架的日志级别类似,可以满足绝大多数场景的日志需求。

package org.apache.ibatis.logging;

/**
 * 日志接口
 * @author Clinton Begin
 */
public interface Log {
  /**
   * debug是否启用
   * @return
   */
  boolean isDebugEnabled();
  /**
   * trace是否启用
   * @return
   */
  boolean isTraceEnabled();
  /**
   * error级日志打印
   * @param s 日志消息
   * @param e errors Or exceptions的堆栈消息
   */
  void error(String s, Throwable e);
  /**
   * error级日志打印
   * @param s 日志消息
   */
  void error(String s);
  /**
   * debug级日志打印
   * @param s 日志消息
   */
  void debug(String s);
  /**
   * trace级日志打印
   * @param s 日志消息
   */
  void trace(String s);
  /**
   * warn级日志打印
   * @param s 日志消息
   */
  void warn(String s);

}

五、LogFactory类

  LogFactory 工厂类负责创建对应的日志组件适配器。

  1. 字段
 /**
   * Marker to be used by logging implementations that support markers
   */
  public static final String MARKER = "MYBATIS";
  /**
   * 记录当前使用的第三方日志组件所对应的适配器的构造方法
   */
  private static Constructor<? extends Log> logConstructor;
  1. 静态代码块
      静态代码块通过执行trylmplementation()方法依次尝试获取SLF4J、Apache Commons Logging、Log4j2、Log4j、JDK logging类型的日志实现。trylmplementation()方法首先会检测logConstructor字段, 若为空则调用Runnable.run(),其中会调用use*Logging()方法,尝试获取对应的Log实现。
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
      }
    }
  }
  1. use*Logging()方法
      use*Logging()方法,尝试获取对应的Log日志实现。其中,通过setImplementation()方法获取对应日志实现类的构造器,并对字段logConstructor进行初始化。下面以useLog4J2Logging()方法为例,来介绍代码:
public static synchronized void useLog4J2Logging() {
    setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
  }
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);
    }
  }

六、日志适配器类

在这里插入图片描述
  在LogFactory类中,通过setImplementation()方法实现字段logConstructor的初始化。其中该方法的参数就是指定日志适配器的完全限定名,通过反射机制,获取对应的构造函数。下面以log4j2和log4j为例,介绍其中的用法。
  其中,Log4jImpl 就是对接口Log的实现。log4j2的实现稍微有些差别,它对应的适配器类是Log4j2Impl,其中根据获取Logger实例对象的类进行判断,如果是AbstractLogger类型,就通过Log4j2AbstractLoggerImpl来实例化Log对象,否则使用Log4j2LoggerImpl实例化对象。和log4j2用法一样的适配器,还有slf4j,其他的都和Log4j使用方式一样。

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 boolean isDebugEnabled() {
    return log.isDebugEnabled();
  }

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

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

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

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

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

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

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

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68914.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!