偶然发现一个问题,记录一下以备查询。

问题:系统启动时发现日志初始化了两次

14:28:04.798 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. 14:28:04.970 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.

相关环境文件

  • mybatis-config.xml片段:
<settings>
  <setting name="logImpl" value="SLF4J" />      <!-- 日志实现包 -->
</settings>
  • applicitionContext.xml片段:
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="driverManagerDataSource" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>

既然是LogFactory打印的日志,先从LogFactory开始看,

public final class LogFactory { 
    private static Constructor<? extends Log> logConstructor; 
    static {                   // 加载时按顺序尝试日志实现,这是第一条日志的输出
        tryImplementation(new Runnable() { 
            public void run() { 
                useSlf4jLogging(); 
            } 
        }); 

        tryImplementation(new Runnable() { 
            public void run() { 
                useCommonsLogging(); 
            } 
        });   // 忽略部分代码
    
        tryImplementation(new Runnable() { 
            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(new Object[] { 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 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) {   // 当加载日志实现成功一次后,这里logConstructor已经不为null
            try { 
                runnable.run(); 
            } catch (Throwable t) { // ignore
            } 
        } 
    }

    private static void setImplementation(Class<? extends Log> implClass) { 
        try { 
            Constructor<? extends Log> candidate = implClass.getConstructor(new Class[] { String.class }); 
            Log log = candidate.newInstance(new Object[] { LogFactory.class.getName() }); 
            log.debug("Logging initialized using '" + implClass + "' adapter."); 
            logConstructor = candidate; 
        } catch (Throwable t) { 
            throw new LogException("Error setting Log implementation.  Cause: " + t, t); 
        } 
    } 
}

可以看出在spring容器启动时,已经加载了一个org.apache.ibatis.logging.slf4j.Slf4jImpl

那么第二条日志是什么时候输出呢?

容器在构造SqlSessionFactoryBean时,需要mybatis-config.xml,因为有<setting name="logImpl" value="SLF4J" />这一条配置,

所以在解析到这一条时调用了org.apache.ibatis.session.Configuration这个类又执行了一次初始化,

顺序 SqlSessionFactoryBean -> SqlSessionFactoryBuilder -> XMLConfigBuilder -> Configuration

最终 Configuration 中:

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

由此也看明白了配置日志实现相当于手动执行org.apache.ibatis.logging.LogFactory.useSlf4jLogging(),mybatis文档中也写到了:

如果要手动调用LogFactory.use***Logging()方法,请在调用所有其他MyBatis方法前调用它。另外,只有在相应日志实现存在 的前提下,调用对应的方法才是有意义的,否则MyBatis一概忽略。如你环境中并不存在Log4J,你却调用了 相应的方法,MyBatis就会忽略这一调用,代之默认的查找顺序查找日志实现。