正文开始
读源码最好的方式是带着疑问去解析,这样的话你会更理解源码每一步的意义。
疑问:
为什么 slf4j 能整合不同的日志框架?
源码解析开始
1. 首先搭建slf4j_demo
创建一个maven项目,在pom文件引入
<!--
这个包中已经整合了 logback-classic-1.2.3.jar ,logback-core-1.2.3.jar,slf4j-api-1.7.25.jar
-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
在 src\main\resources 目录下添加文件logback.xml :
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- ch.qos.logback.core.ConsoleAppender 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</Pattern>
</encoder>
</appender>
<!-- 日志级别 -->
<root level="debug">
<appender-ref ref="console" />
</root>
</configuration>
编写一个测试demo
public class DemoController {
static Logger logger = LoggerFactory.getLogger(DemoController.class);
public static void main(String[] args) {
logger.trace("-----trace-----");
logger.debug("-----debug-----");
logger.info("-----info-----");
logger.warn("-----warn-----");
logger.error("-----error-----");
}
}
debug运行起来,这时候控制台打印
08:45:50.856 [main] DEBUG com.cn.controller.DemoController - -----debug-----
08:45:50.857 [main] INFO com.cn.controller.DemoController - -----info-----
08:45:50.858 [main] WARN com.cn.controller.DemoController - -----warn-----
08:45:50.858 [main] ERROR com.cn.controller.DemoController - -----error-----
打印这些日志就代表,slf4j已经成功使用啦。
ps:需要注意的是logger 和 loggerFactory 都是slf4j里的包
2. 源码解析开始
不管读什么源码都要先找到一个入口,然后从入口开始循序渐进的分析,在上面这段代码中,LoggerFactory.getLogger 是用来初始化Logger对象的所以这个代码就是入口,所以我们就从这开始分析
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
第2行:用来初始化logger对象的
第3行~第10行:用来检测记录器名称是否匹配
在此我们的重点是怎么获取logger对象,所以我们点击第二行代码
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
这里面是通过 getILoggerFactory() 初始化一个 loggerFactory ,然后根据loggerFactory获取 logger对象,这里使用了工厂设计模式,当开始阅读源码时会发现设计模式无处不在,我们继续往下走getILoggerFactory
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
第2行~第9行: 判断loggerFactory 是否已经初始化,如果没有初始化就开始执行初始化performInitialization(),这里使用单例模式,保证 loggerFactory 只被初始化一次。
第10行 ~ 第20:判断 loggerFactory 初始化状态,成功、失败等,从字面意思上就可以理解,从这里看出一般都会静态变量来做为状态码,比如 INITIALIZATION_STATE ,这还使用volatile修饰符保证变量线程间的一致性
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
然后我们继续往下走
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
重点来了!bind()方法是绑定loggerFactory的实现,往下走
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
第7行:findPossibleStaticLoggerBinderPathSet()是绑定的重点
第8行:是检查是否两个logger日志绑定器,这就是为什么 slf4j只能有且仅有一种日志框架的binding,在这里做校验了
第11行:是初始化StaticLoggerBinder绑定器对象,这个对象是用来创建loggerFatory,在前面getILoggerFactory() 方法中有使用 StaticLoggerBinder.getSingleton().getLoggerFactory()
然后到这里我们已经知道 loggerFactory工厂是怎么创建的,但是StaticLoggerBinder 是怎么加载的,我们不知道,所以我们到findPossibleStaticLoggerBinderPathSet()里看
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
这里就是精髓了, 通过ClassLoader.getResources 去项目的资源目录下寻找org/slf4j/impl/StaticLoggerBinder.class 文件,如果找到多个的StaticLoggerBinderclass文件,就会在reportMultipleBindingAmbiguity中判断,并将每个StaticLoggerBinder文件路径打印出来。这里就解释了为什么slf4j 不能同时整合多个日志框架。
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Document/Service/Maven/apache-maven-3.6.3/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Document/Service/Maven/apache-maven-3.6.3/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
3. 最后总结
slf4j 提供了统一的 logger api ,每个日志框架都会实现logger api。在调用logger的时候 ,slf4j 会根据ClassLoader 加载各框架的StaticLoggerBinder ,然后根据StaticLoggerBinder获取对应的loggerFactory对象,在根据LoggerFactory 创建Logger对象,所以调用logger的方法时,实际是调用各个框架logger方法,这实际上就是门面模式的一种实现。
这就解释了为什么slf4j能整合不同日志框架,实际slf4j只是提供了了一个接口规范,具体实现是交给对应的日志框架实现的。