1、使用已有的日志组件。它会尝试加载项目依赖的日志组件,加载到哪一个就使用哪一个。
2、加载时遵循优先级,按照优先级加载,优先级如下:slf4j > common logging > log4j2 > log4j > jdk logging > 没有日志
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);
}
slf4j包含2个适配者,Slf4jLocationAwareLoggerImpl和Slf4jLoggerImpl,并且这两个类也实现了目标接口,因此在后面的适配器Slf4jImpl中直接使用Log在内部持有他们(看源码注释,有两个类是因为在JDK的不同版本有所不同)
Slf4jLocationAwareLoggerImpl
/**
* @author Eduardo Macarron
* 适配者,将目标接口Log的方法调用转换为LocationAwareLogger自身logger实例的方法调用
*/
class Slf4jLocationAwareLoggerImpl implements Log {
private static Marker MARKER = MarkerFactory.getMarker(LogFactory.MARKER);
private static final String FQCN = Slf4jImpl.class.getName();
private LocationAwareLogger logger;
Slf4jLocationAwareLoggerImpl(LocationAwareLogger logger) {
this.logger = logger;
}
/**
* 实现Log接口,并重写对应的方法,在方法内部调用的是slf4j的日志实现,
*/
@Override
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return logger.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
logger.log(MARKER, FQCN, LocationAwareLogger.ERROR_INT, s, null, e);
}
@Override
public void error(String s) {
logger.log(MARKER, FQCN, LocationAwareLogger.ERROR_INT, s, null, null);
}
@Override
public void debug(String s) {
logger.log(MARKER, FQCN, LocationAwareLogger.DEBUG_INT, s, null, null);
}
@Override
public void trace(String s) {
logger.log(MARKER, FQCN, LocationAwareLogger.TRACE_INT, s, null, null);
}
@Override
public void warn(String s) {
logger.log(MARKER, FQCN, LocationAwareLogger.WARN_INT, s, null, null);
}
}
/**
* @author Eduardo Macarron
* 适配者,将目标接口Log的方法调用转换为Slf4jLoggerImpl自身log实例的方法调用
*/
class Slf4jLoggerImpl implements Log {
private Logger log;
public Slf4jLoggerImpl(Logger logger) {
log = logger;
}
/**
* 实现Log接口,并重写对应的方法,在方法内部调用的是slf4j的日志实现,
*/
@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);
}
}
public class Slf4jImpl implements Log {
//1.这里的Log是用于传入适配者
//这里有点特殊,一般来说适配者不一定实现了目标接口,比如第三方支付的时候第三方支付接口和自己使用的目标接口一般就不一样,
//因此这里持有的就是第三方的接口,但是在这里Slf4jLocationAwareLoggerImpl和Slf4jLoggerImpl这两个适配者也实现了Log
//接口,因此就统一起来了,不管是哪一个适配者都是用同一个接口来接收,其实即便Slf4jLocationAwareLoggerImpl没有实现Log
//接口也没有问题,他实现了A接口,这里用A接收就好了,调用的时候转换调用它的接口即可,整体来看就是适配器模式的经典运用
private Log log;
//2.通过构造方法传入是配置,对于slf4j来说,这里有2中可能,因此适配者有2种可能(看注释好像和版本有关)
//分别对应Slf4jLocationAwareLoggerImpl和Slf4jLoggerImpl
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
//3.大于1.6的版本,适配者是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 e) {
// fail-back to Slf4jLoggerImpl
} catch (NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
//4.小于1.6的版本,适配者是Slf4jLoggerImpl
log = new Slf4jLoggerImpl(logger);
}
/**
* Slf4jImpl是适配器,因此
* 1.实现Log接口,并重写对应的方法,便于对外调用
* 2.在方法内部调用的是slf4j的日志实现,是Slf4jLoggerImpl或者Slf4jLocationAwareLoggerImpl
* 我们最初说过Mybatis的Log接口只是定义了自己想要的功能而已,功能的实
* 现自己并不会去做,而是绑定第三方日志组件之后交由第三方组件去做,这里
* 看的很清楚了,就是交给slf4j组件去做。
*/
@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);
}
}
/**
* 适配器,将目标接口Log的方法调用转换为Logger自身log实例的方法调用
*/
public class Log4jImpl implements Log {
private static final String FQCN = Log4jImpl.class.getName();
private 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);
}
}
public final class LogFactory {
/**
* Marker to be used by logging implementations that support markers
* 给支持marker功能的logger使用(目前有slf4j, log4j2)
*/
public static final String MARKER = "MYBATIS";
/**
* 存放绑定的日志框架的构造方法;(绑定哪个日志框架,就把这个日志框架所对应logger的构造函数放进来)
*/
private static Constructor<? extends Log> logConstructor;
/**
* 1.静态代码块,用来完成Mybatis和第三方日志框架的绑定过程
* 2.优先级别是 slf4j > common logging > log4j2 > log4j > jdk logging > 没有日志
* 3.执行逻辑是:按照优先级别的顺序,依次尝试绑定对应的日志组件,一旦绑定成功,后面的就不会再执行了。
* 我们看tryImplementation方法,tryImplementation方法首先会判断logConstructor是否为空,为空则尝试绑定,
* 不为空就什么都不做(不空说明已经绑定成功)。
* 4.假如第一次进来绑定slf4j,logConstructor肯定为空,那么在useSlf4jLogging方法的逻辑里面就会将slf4j的构造方法放到logConstructor里面去,
* 后面再执行common logging的绑定流程时发现logConstructor不为空,说明前面已经成功初始化了,就不会执行了;
* 反过来假如slf4j绑定失败,比如依赖包没有或者版本之类的报错,那么setImplementation抛出异常,在tryImplementation里面捕获到异常之后会直接
* 忽略,然后就继续尝试绑定common logging,直到成功。这就是绑定的整体流程。
* */
static {
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
}
private LogFactory() {
// disable construction
}
/**
* 对外提供2种获取日志实例的方法,类似于Slf4j的LoggerFactory.getLogger(XXX.class);
*/
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);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
/**
* 1.下面的方法都是类似的,对应于前面绑定几种日志组件的情况,就是把对应的类放到setImplementation方法里面去做
* 具体的绑定细节,细节的处理流程时一样的。优先级降低
*/
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
//2
public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}
//4
public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
//3
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
//5
public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}
//这个好像是测试用的,没看到代码中使用了
public static synchronized void useStdOutLogging() {
setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}
//6
public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}
/**
* 绑定方法;所有尝试绑定的动作都会走这个方法,如果已经有绑定的了,logConstructor就不为null,就不会再尝试绑定了
*/
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
/**
* 绑定的细节
*/
private static void setImplementation(Class<? extends Log> implClass) {
try {
//1.获取绑定类的构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
//2.通过构造方法创建一个实例赋值给Log,因为采用了适配器模式,传进来的都是适配者,适配者本身是实现了目标接口的,
//因此进来的类都是Log接口的子类,这是一个多态的写法
Log log = candidate.newInstance(LogFactory.class.getName());
//3.这里第2步的赋值只是为了在这里打印日志,打印提示初始化适配器的类型
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
//3.把绑定的日志组件的构造方法放到logConstructor里面,后面就不会再尝试绑定其他的日志组件了
logConstructor = candidate;
} catch (Throwable t) {
//4.抛出的异常会在tryImplementation方法中捕获,捕获之后会尝试绑定下一个日志组件
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
/**
* invoke方法,动态代理的核心方法
*
* */
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
//1.如果是Object定义的方法,使用当前对象直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
/**
* 2.如果是:"execute"、"executeUpdate"、"executeQuery"或者"addBatch"方法,那就打印日志
* EXECUTE_METHODS.add("execute");
* EXECUTE_METHODS.add("executeUpdate");
* EXECUTE_METHODS.add("executeQuery");
* EXECUTE_METHODS.add("addBatch");
* */
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
debug(" Executing: " + removeBreakingWhitespace((String) params[0]), true);
}
if ("executeQuery".equals(method.getName())) {
//3.如果是executeQuery查询方法会返回ResultSet,那就执行之后,将ResultSet包装成一个具备日志功能的ResultSetLogger
ResultSet rs = (ResultSet) method.invoke(statement, params);
//4.返回具备日志能力的ResultSetLogger
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
//5.如果不是executeQuery方法(其余三个方法都是返回int,不需要包装),那就直接调用,不需要使用增强了日志功能的对象
return method.invoke(statement, params);
}
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
//6.如果返回的ResultSet不是null,那就返回一个ResultSet的代理对象,一个具备日志打印能力的ResultSet
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
//7.如果是其他方法,就直接调用,不需要使用增强了日志功能的对象
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}