一. 指定Mybatis日志框架
1.在mybatis-config.xml中配置如下
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>
2.mybatis启动时会解析xml,通过XMLConfigBuilder中parseConfiguration->loadCustomLogImpl->resolveClass->
configuration.setLogImpl
1.//解析配置文件
private void parseConfiguration(XNode root) {
try {
省略
// 处理<settings>标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 处理Log
loadCustomLogImpl(settings);
省略
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.执行loadCustomLogImpl
private void loadCustomLogImpl(Properties props) {
//调用父类中resolveClass方法
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
3. //根据别名解析class
protected <T> Class<? extends T> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
}
4. //从typeAliasRegistry获取Class
protected <T> Class<? extends T> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
5 //而typeAliasRegistry是在Configuration类 new对象时执行
public Configuration() {
省略
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);
省略
}
//此处使用LogFactory设置
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
//使用用户指定日志适配器
LogFactory.useCustomLogging(this.logImpl);
}
}
3.关于LogFactory类
日志工厂静态初始化时默认加载日志适配器,根据适配器所需要对应的日志框架Class 如果对应的Class存在则加载一个日志适配器,上面的LogFactory.useCustomLogging(this.logImpl); 使用用户指定的日志适配器替换默认加载的适配器
// 记录当前使用的第三方日志库适配器的构造方法
private static Constructor<? extends Log> logConstructor;
//静态初始化
static {
// 尝试按照Slf4j、Common Loggin、Log4j2、Log4j、Jdk Logging、No Logging的顺序,
// 依次加载对应的适配器,一旦加载成功,就会记录到logConstructor字段中,并会停止后续适配器
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
//tryImplementation logConstructor不存在则执行对应的LogFactory::xxxLogging操作
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
使用Slf4j加载举例
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
//根据传入的适配器Class文件调用仅含一个字符串的构造方法,如果实例化成功则加载成功
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 获取implClass这个适配器的构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 尝试加载implClass这个适配器,加载失败会抛出异常
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);
}
}
// Slf4jImpl适配器构造如下
public Slf4jImpl(String clazz) {
//注意此时的Logger、LoggerFactory包路径为org.slf4j
Logger logger = LoggerFactory.getLogger(clazz);
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
log = new Slf4jLoggerImpl(logger);
}
上面Logger logger = LoggerFactory.getLogger(clazz); 使用的是org.slf4j包中的类,在mybtais源码中通过maven引入slf4j ,通过true控制即不传递依赖,在应用中需要手动依赖org.slf4j包,Slf4jImpl适配器才能加载到Logger logger = LoggerFactory.getLogger(clazz);才能正确实例化,静态方法加载其他适配器也是如此,mybatis源码中的 org.slf4j坐标如下
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<optional>true</optional>
</dependency>