这是我参与8月更文挑战的第三天,活动详情查看:8月更文挑战
功能介绍
jcl全称为Jakarta Commons Logging,是Apache提供通用日志的API, 而spring-jcl模块大概的意思是:属于spring自己的日志API,不过其底层还是采用的是Apache的那套机制。
具体分析
log.java
只定义log接口,具体的实现则是在运行时动态寻找,比较有意思的是为了向下兼容,spring团队将包名与之前的也保持一致了,而并不是以spring进行命名。
// 注意这里的包名
package org.apache.commons.logging;
public interface Log {
/**是否开启某个级别的日志*/
boolean isFatalEnabled();
boolean isErrorEnabled();
boolean isWarnEnabled();
boolean isInfoEnabled();
boolean isDebugEnabled();
boolean isTraceEnabled();
/**提供不同级别的日志方法*/
void fatal(Object message);
void fatal(Object message, Throwable t);
void error(Object message);
void error(Object message, Throwable t);
void warn(Object message);
void warn(Object message, Throwable t);
void info(Object message);
void info(Object message, Throwable t);
void debug(Object message);
void debug(Object message, Throwable t);
void trace(Object message);
void trace(Object message, Throwable t);
LogFactor.java
通过LogFactory进行实现,另外还描述到性能的问题,建议使用以下方式进行记录
if (log.isDebugEnabled()) {
... do something expensive ...
log.debug(theResult);
}
ok,我们接着往下看LogFactory类的实现参考jcl-ovre-slf4j桥接工具,提供了2种方式进行实现,弃用的直接跳过。
public abstract class LogFactory {
/**根据类名实例化*/
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
/**根据字符串实例化*/
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
但是该类为抽象类,最终是利于spi机制进行实例化,即通过一个抽象工厂类LogFactory实现配置文件的加载解析以及实例化日志框架。
LogAdapter.java
而LogAdapter是最核心的部分,负责创建并实例化上面的log接口,在进行实例化LogAdapter时,会自动去调用Class.forName找到该类并加载到jvm中,并且执行对应的静态代码块。接着利用ClassLoader加载LogAdapter,通过ClassNotFoundException异常去判断使用哪个日志框架,默认是使用java.util.logging,如下所示
static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
logApi = LogApi.SLF4J_LAL;
}
else {
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
logApi = LogApi.SLF4J;
}
else {
logApi = LogApi.JUL;
}
}
private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false;
}
}
那么它是如何判断使用哪个实现类的呢?我们继续分析
// 由该类中的内部类进行创建
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
// 实现原理根据LoggerContextFactory找到ContextSelector
// 再获取ContextAnchor类中的ThreadLocal<LoggerContext>
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
// 根据单例模式获取LocationAwareLogger再实例化Slf4jLog
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
// 根据 slf4j自带的LoggerFactory进行实例化
return Slf4jAdapter.createLog(name);
default:
// 根据jdk全局LogManager实例化Logger的子类RootLogger
return JavaUtilAdapter.createLog(name);
}
总结
🆗,spring-jcl模块的解析就告一段落总的来说,那么知识点如下所示
- 设计模式:适配器模式
提供的统一的接口,然后在适配类中将对日志的操作委托给具体的日志框架.为不同的接口实现互通的目的
public interface Log {}
private static class Log4jLog implements Log, Serializable {}
private static class Slf4jLog<T extends Logger> implements Log, Serializable {}
private static class JavaUtilLog implements Log, Serializable {}
public abstract class LogFactory {}
public class LogFactoryService extends LogFactory {}
final class LogAdapter {}
- 设计模式:策略模式
严格来说不算是策略模式,但是又有这方面的思想.主要是利用类名称找到对应的上下文,随着策略改变而改变的,并且返回其父类,解决条件分支过多的情况
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
return JavaUtilAdapter.createLog(name);
}
- 技术点:ThreadLocal
利用
LoggerContext类中的ThreadLocal<LoggerContext>获取LoggerContext对象
- 依赖:最小原则
该模块仅仅只是引入了2个api,做到最小原则的引入依赖,以及相应的描述信息,这点值得笔者去学习
description = "Spring Commons Logging Bridge"
dependencies {
optional("org.apache.logging.log4j:log4j-api")
optional("org.slf4j:slf4j-api")
}