三方日志的初始化
运行时决定第三方日志
LogFactory 类加载时会运行静态代码块,以此尝试 Slf4j、JakartaCommonsLogging、Log4j2、Log4j、Jdk14Log、NoLog 等多种第三方日志,直到 logConstructor 不为 Null,此时就确定了使用哪一种第三方日志。
static {
// 函数式接口,执行顺序 tryImplementation,然后执行 函数表达式,再执行函数表达式中的 setImplementation()
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
/**
* 如果 logConstructor == null,就不会再执行 run(),
* 在 本类上面 static 代码块,会多次调用这个方法,根据调用顺序优先级,只会初始化一个 logConstructor,logConstructor 只会被一个三方日志中的一个初始化。
* @param runnable 作为 java8 的新特性,函数式接口;和 Thread 没有半毛钱关系;
*
*/
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
// run() 函数式接口中方法的执行,比如这个 LogFactory::useSlf4jLogging lambda 表达式
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 三方日志的带有 String 参数的构造器,通过构造器创建 log 实例,并 赋值给 logConstructor
// 获取 String 类型参数 的构造器;因为 Log4j2Impl 等具体第三方构造器的 参数是 String 类型
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// LogFactory.class.getName() 仅作为 log 具体实现类中构造函数的参数;
// 构造器参数
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);
}
}
自定义配置 Log 类
- mybatis-config.xml 配置第三方 Log
- 解析 setting 配置
- 解析配置的 logImpl 三方日志
Configuration 调用 LogFactory.useCustomLogging(this.logImpl); 覆盖 初始化时的第三方 Log 日志
// 1. mybatis-config.xml 配置第三方 Log
<setting name="logImpl" value="LOG4J"/>
// 2. 解析 setting 配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 3. 解析配置的 logImpl 三方日志
loadCustomLogImpl(settings);
/**
* 加载 <setting> 配置的 log 日志实现
* @param props
*/
private void loadCustomLogImpl(Properties props) {
// logImpl 全路径名称
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
// 4. Configuration 调用 LogFactory.useCustomLogging(this.logImpl); 覆盖 初始化时的第三方 Log 日志
// 设置 <setting> 中 第三方 log 的日志实现
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
LogFactory.useCustomLogging(this.logImpl);
}
}
// 加载 <setting> 配置的第三方日志
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
Log 对象的获取
在 LogFactory 工厂类,创建具体的 Log 对象。
根据初始化的 logConstructor 对象实例,获取具体的 Log 对象,用来记录日志
/**
* 样例:private static final Log log = LogFactory.getLog(BaseExecutor.class);
*
* 获取的 Log 对象实例的构造方法的参数包含类名称,所以有了 aClass 这个参数
* 这个参数用来记录,哪个类,使用了 log 对象
* 返回 log 对象
* @param aClass 使用 log 对象的类名称
* @return
*/
public static Log getLog(Class<?> aClass) {
// 类名称
return getLog(aClass.getName());
}
/**
* 根据一个类名称,返回一个 Log 对象
* logConstructor 是项目初始化的时候,进行了创建赋值
* @param logger 构造器的参数,要记录日志的类的名称
* @return
*/
public static Log getLog(String logger) {
try {
// logger 是 构造器参数
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
日志适配器
第三方日志组件都有各自的 Log 级别,且都有所不同, Log4j2 则提供了 trace debug info warn error fatal 这六种日志级别。 MyBatis 提供了 trace debug warn error 四个级别。
三方日志 UML 图
image.pngLogFactory
LogFactory 工厂类负责创建对应的日志组件适配器 。
此类 第一步:创建 logConstructor 对象实例,然后根据 logConstructor 对象实例获取具体的 Log 对象,用来记录日志。
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; // 记录当前使用的第三方日志组件所对应的适配器的构造方法
// 以此从上之下,尝试初始化第三方日志模块,直到 logConstructor != null
static {
// 函数式接口,执行顺序 tryImplementation,然后执行 函数表达式,再执行函数表达式中的 setImplementation()
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
/**
* 样例:private static final Log log = LogFactory.getLog(BaseExecutor.class);
*
* 获取的 Log 对象实例的构造方法的参数包含类名称,所以有了 aClass 这个参数
* 这个参数用来记录,哪个类,使用了 log 对象
* 返回 log 对象
* @param aClass 使用 log 对象的类名称
* @return
*/
public static Log getLog(Class<?> aClass) {
// 类名称
return getLog(aClass.getName());
}
/**
* 根据一个类名称,返回一个 Log 对象
* logConstructor 是项目初始化的时候,进行了创建赋值
* @param logger 构造器的参数,要记录日志的类的名称
* @return
*/
public static Log getLog(String logger) {
try {
// logger 是 构造器参数
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
// 加载 <setting> 配置的第三方日志
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 useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
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);
}
public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}
/**
* 如果 logConstructor == null,就不会再执行 run(),
* 在 本类上面 static 代码块,会多次调用这个方法,根据调用顺序优先级,只会初始化一个 logConstructor,logConstructor 只会被一个三方日志中的一个初始化。
* @param runnable 作为 java8 的新特性,函数式接口;和 Thread 没有半毛钱关系;
*
*/
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
// run() 函数式接口中方法的执行,比如这个 LogFactory::useSlf4jLogging lambda 表达式
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 三方日志的带有 String 参数的构造器,通过构造器创建 log 实例,并 赋值给 logConstructor
// 获取 String 类型参数 的构造器;因为 Log4j2Impl 等具体第三方构造器的 参数是 String 类型
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// LogFactory.class.getName() 仅作为 log 具体实现类中构造函数的参数;
// 构造器参数
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);
}
}
}
Jdk14LoggingImpl 适配器
Jdk14LoggingImpl 实现了 org.apache.ibatis logging.Log 接口,并封装了 java.util.logging.Logger对象, org.apche.ibatis.logging.Log 接口的功能全部通过调用 java.util.logging.Logger 对象现, 这就是前面介绍的适配器模式。
public class Jdk14LoggingImpl implements Log {
// 底层封装的 java.util.logging.Logger 对象
private final Logger log;
// 通过 logConstructor.newInstance(logger) 初始化 java.util.logging.Logger 对象; logger 为调用 log 日志的类名称
public Jdk14LoggingImpl(String clazz) {
log = Logger.getLogger(clazz);
}
@Override
public boolean isDebugEnabled() {
return log.isLoggable(Level.FINE);
}
@Override
public boolean isTraceEnabled() {
return log.isLoggable(Level.FINER);
}
@Override
public void error(String s, Throwable e) {
log.log(Level.SEVERE, s, e);
}
@Override
public void error(String s) {
log.log(Level.SEVERE, s);
}
@Override
public void debug(String s) {
log.log(Level.FINE, s);
}
@Override
public void trace(String s) {
log.log(Level.FINER, s);
}
@Override
public void warn(String s) {
log.log(Level.WARNING, s);
}
}
网友评论