Mybatis内部提供了日志模块,用于记录框架内部异常与调试信息;日志实现使用了经典的适配器模式,mybatis自己本身不提供日志功能,而是依赖其他的日志框架来实现;内部支持的优先级是:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
由于各个厂商提供的日志api,日志级别等都不尽相同,所以mybatis对所有支持的日志框架做了封装,自定义了日志打印级别:
error (错误) > warn (警告) > debug(调试信息)>trace
mybatis 里定了了统一的日志接口,Log接口并定义了一系列的接口(目标接口)
/**
* @author Clinton Begin
*/
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);
}
提供的实现类(适配器):
日志集成核心类 org.apache.ibatis.logging.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 {
// 在LogFactory的静态方法里按照特定的顺序,寻找并加载对于的第三方日志组件,
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
/*
* 获取Log类对象。这个是为了实例化一个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);
}
}
/*
* tryImplementation 方法里会先判断是否已经加载了日志组件,如果已经加载了则不会继续处理;
* 并且这个方法做了异常的吞没,如果没有对于的日志组件也不会影响下一个组件的加载流程;
*/
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
/*
* 这个方法主要功能是去加载提供的实现类,这些实现类里引用了第三方的类,
* 如果项目中没有提供对于的jar包则该方法会抛错,然后异常会传递到 tryImplementation方法;
* 如果实例化特制类成功,则会将对于的类构造器赋值给 logConstructor,以便后续实例化
*/
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);
}
}
}
下面以 org.apache.ibatis.logging.log4j.Log4jImpl 为例做分析
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);
}
}
这个类适配的 org.apache.log4j日志组件,在类里有一个成员变量。相当于适配器模式里面对象的适配器模式。Log4jImpl这个类相当于是将log4j的功能翻译成了Mybatis的功能,做了api的适配
拓展:Mybatis是如何做到日志可以自定义扩展的呢?
从上面的源码分析我们已经看出了,Mybatis内置了
- SLF4J
- Apache Commons Logging
- Log4j2
- Log4j2
- JDK Logging
几种开源的实现作为日志扩展。默认使用的是 “Slf4j ” 如果我们需要自己指定也是可以的,我们只需要在配置文件中加上:
<configuration>
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
</configuration>
mybatis的启动加载的源码中会自动的加载,解析配置文件,
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 4、根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2
loadCustomLogImpl(settings);
...
}
}
/**
* 在这里我们可以指定日志的实现方式
* @param props mybatis-config.xml中setting的properties的配置文件中logImpl实现方式
*/
private void loadCustomLogImpl(Properties props) {
// 根据配置文件中的配置的日志的名称,解析出使用日志的实现类
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
// 方法设置为我们自定义的类,跟踪此方法,找到最后的实现为
configuration.setLogImpl(logImpl);
}
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
// 查找字符串参数的构造器
LogFactory.useCustomLogging(this.logImpl);
}
}
// 最后到了最关键的 LogFactory# useCustomLogging方法,这个方法如果在加载配置文件阶段就指定了
// 那么我们自定义的日志实现类就会生效
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}