Mybatis--日志模块

本文详细解析了MyBatis日志模块的工作原理,包括LogFactory类的构造方法、日志实现类的选择过程,以及如何通过不同日志框架如Slf4j、Log4j进行日志记录。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

 Mybatis的日志模块对应的是logging包,如下图所示:

                              

          日志模块所涉及的类如下图所示:

                                                         

从图的上面部分,是非常多的 Logger 类的实现,分别对应我们常用的日志框架 Log4j、Slf4j ,这就是 MyBatis 对这些日志框架的适配。

从图的下面部分,是 BaseJdbcLogger 以及其四个子类,这个并不是将日志打印到数据库,而是 MyBatis 通过 JDK 动态代理的方式,将 JDBC 的操作,打印到日志中。

2、LogFactory

org.apache.ibatis.logging.LogFactory ,Log 工厂类。

     2.1构造方法 

/**
 * Marker to be used by logging implementations that support markers
 */
public static final String MARKER = "MYBATIS";

/**
 * 使用的 Log 的构造方法
 */
private static Constructor<? extends Log> logConstructor;

static {
    // 逐个尝试,判断使用哪个 Log 的实现类,即初始化 logConstructor 属性
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
}

private LogFactory() {
    // disable construction
}

 static代码块中,可以看到,按照 Slf4j、CommonsLogging、Log4J2Logging、Log4JLogging、JdkLogging、NoLogging 的顺序,逐个尝试,判断使用哪个 Log 的实现类,即初始化 logConstructor属性。

tryImplementation(Runnable runnable) 方法,判断使用哪个 Log 的实现类。代码如下:

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

当 logConstructor 为空时,执行 runnable 的方法。那么,runnable 怎么来的呢。实际上,static代码块 处,使用了 Lambda 表达式,即 tryImplementation(LogFactory::useSlf4jLogging) 代码块,对应为:

tryImplementation(new Runnable() {
    @Override
    public void run() {
        LogFactory.useSlf4jLogging();
    }
});

useSlf4jLogging() 方法,尝试使用 Slf4j 。代码如下:

public static synchronized void useSlf4jLogging() {
    setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
  }

setImplementation(Class<? extends Log> implClass) 方法,尝试使用指定的 Log 实现类,例如此处为 org.apache.ibatis.logging.slf4j.Slf4jImpl 。代码如下:

private static void setImplementation(Class<? extends Log> implClass) {
    try {
        // 获得参数为 String 的构造方法
        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.");
        }
        // 创建成功,意味着可以使用,设置为 logConstructor
        logConstructor = candidate;
    } catch (Throwable t) {
        throw new LogException("Error setting Log implementation.  Cause: " + t, t);
    }
}

当然,也可以通过 useCustomLogging(Class<? extends Log> clazz) 方法,设置自定义的 Log 实现类。代码如下:

public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
    setImplementation(clazz);
  }

             2.2、getLog

getLog 方法,获得 Log 对象。代码如下: 

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

3、Log

org.apache.ibatis.logging.Log ,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);

}

3.1 StdOutImpl 

  org.apache.ibatis.logging.stdout.StdOutImpl ,实现 Log 接口,StdOut 实现类。代码如下:

  

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

基于 System.out 和 System.err 来实现。

           3.2 Slf4jImpl 

org.apache.ibatis.logging.slf4j.Slf4jImpl ,实现 Log 接口,Slf4j 实现类。代码如下:


public class Slf4jImpl implements Log {

    private Log log;

    public Slf4jImpl(String clazz) {
        // 使用 SLF LoggerFactory 获得 SLF Logger 对象
        Logger logger = LoggerFactory.getLogger(clazz);

        // 如果是 LocationAwareLogger ,则创建 Slf4jLocationAwareLoggerImpl 对象
        if (logger instanceof LocationAwareLogger) {
            try {
                // check for slf4j >= 1.6 method signature
                logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
                log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
                return;
            } catch (SecurityException | NoSuchMethodException e) {
                // fail-back to Slf4jLoggerImpl
            }
        }

        // Logger is not LocationAwareLogger or slf4j version < 1.6
        // 否则,创建 Slf4jLoggerImpl 对象
        log = new Slf4jLoggerImpl(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);
    }

}

 

在构造方法中,可以看到,适配不同的 SLF4J 的版本,分别使用Slf4jLocationAwareLoggerImpl和Slf4jLoggerImpl 类。 

4.测试用例 

       最后我们看下官方给出的测试用例LogFactoryTest,代码如下:

public class LogFactoryTest {

  @Test
  public void shouldUseCommonsLogging() {
    LogFactory.useCommonsLogging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), JakartaCommonsLoggingImpl.class.getName());
  }

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

  @Test
  public void shouldUseLog4J2() {
    LogFactory.useLog4J2Logging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), Log4j2Impl.class.getName());
  }
  
  @Test
  public void shouldUseJdKLogging() {
    LogFactory.useJdkLogging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), Jdk14LoggingImpl.class.getName());
  }

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

  @Test
  public void shouldUseStdOut() {
    LogFactory.useStdOutLogging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), StdOutImpl.class.getName());
  }

  @Test
  public void shouldUseNoLogging() {
    LogFactory.useNoLogging();
    Log log = LogFactory.getLog(Object.class);
    logSomething(log);
    assertEquals(log.getClass().getName(), NoLoggingImpl.class.getName());
  }

  @Test
  public void shouldReadLogImplFromSettings() throws Exception {
    try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/logging/mybatis-config.xml")) {
      new SqlSessionFactoryBuilder().build(reader);
    }

    Log log = LogFactory.getLog(Object.class);
    log.debug("Debug message.");
    assertEquals(log.getClass().getName(), NoLoggingImpl.class.getName());
  }

  private void logSomething(Log log) {
    log.warn("Warning message.");
    log.debug("Debug message.");
    log.error("Error message.");
    log.error("Error with Exception.", new Exception("Test exception."));
  }

}

测试结果:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值