从MyBatis官方文档学习源码-日志实现


Mybatis日志实现

1. 首先看下源码包的结构:

logging包结构图

可以大致看出Mybatis支持7种不同的日志实现。

说到Java日志不得不提SLF4J与apache的JCL属于日志门面,提供统一的日志操作规范,输入日志功能由具体的日志实现框架完成(log4j、log4j2、JUL、logback等)。

日志框架实现关系

2. 源码阅读

Mybatis通过org.apache.ibatis.logging.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);

}

针对不同的日志框架提供对Log接口的实现,如下图:
Log接口实现关系
7种不同实现:
Apache Commons Logging:
Log4J:
Log4J2:
Java Util Logging:
No Logging:
SLF4J:
Stdout:

以Log4J为例,Log现实类逻辑如下:


import org.apache.ibatis.logging.Log;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class Log4jImpl implements Log {

  private static final String FQCN = Log4jImpl.class.getName();

  // Log4J接口类 
  private final Logger log;

  public Log4jImpl(String clazz) {
    log = Logger.getLogger(clazz);
  }
  ...//省略
}

可以看出构造方法中,获取Log4J框架中的Logger对象,然后将日志输出操作委托给Logger类的对象log来完成。其他日志实现与之类似。

MyBatis框架如何使用日志呢?以org.apache.ibatis.logging.LogFactoryTest作参考:

  @Test
  void shouldUseLog4J() {
    LogFactory.useLog4JLogging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), Log4jImpl.class.getName());
  }

从代码片段看 LogFactory,很明显MyBatis框架Log采用工厂模式创建。我们先来看看LogFactory.useLog4JLogging();

  public static synchronized void useLog4JLogging() {
    setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
  }
public final class LogFactory {

  // 当前使用的日志实现类的Constructor
  private static Constructor<? extends Log> logConstructor;
  
  
  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框架。

我们发现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 {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

  private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

发现这个静态代码块比较特别,再接着看tryImplementation(),发现方法参数是一个Runnable的匿名对象,很容易联想Java的多线程,但是仔细一看这里知识把run()方法作为普通方法调用,所以不涉及到多线程。

MyBatis日志模块设计比较巧妙的地方就是我们没有指定使用哪种日志实现,MyBatis能够按照顺序查找Classpath下的日志框架相关Jar包。大胆推测加载顺序SLF4J->JCL->Log4J2->Log4J->JUL->No Logging。小心证实,如果Classpath中不存在Log4J,则setImplementation()方法中 Constructor<? extends Log> candidate = implClass.getConstructor(String.class); 会抛出ClassNotFoundException异常或者NoClassDefFoundError错误。进而在catch中普获重新封装并且抛出

 throw new LogException("Error setting Log implementation.  Cause: " + t, t);

在tryImplementation()方法中,进行进一步捕获,不做任何处理,然后查找下一个日志框架JAR包是否存在,直至找到为止。

3. MyBatis使用日志方式

在使用MyBatis时候,可以通过setting属性中的logImpl参数指定哪种框架输出日志。

<settings>
		<setting name="logImpl" value="LOG4J"/>
</settings>

在这里插入图片描述
logImpl属性限定值有SLF4J | LOG4J2 | LOG4J | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
或者日志实现类的完全限定名,原因是Configuration类的构造方法中,为这些日志实现类注册了别名,代码如下:

public class Configuration {

   //日志属性
   protected Class<? extends Log> logImpl;


 public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

  ...//省略

    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", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

   ...//省略
  }

  public void setLogImpl(Class<? extends Log> logImpl) {
    if (logImpl != null) {
      this.logImpl = logImpl;
      LogFactory.useCustomLogging(this.logImpl);
    }
  }

当框架启动时,解析主配置文件中的logImpl参数,然后调用setLogImpl()方法中设置日志实现。MyBatis中所有的Log实例都是由LogFactory创建的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值