概要
看了之前的文章Java日志体系总结后,相信大家对slf4j以及其他日志组件的关系有了一定理解。slf4j只是为日志的输出提供了统一接口,并没有具体的实现,就好像JDBC一样。那么,大家会不会好奇slf4j是怎么绑定/适配/桥接到log4j或者logback其他日志实现组件的呢?这篇文章为大家详细讲述。
适配过程原理
统计API接口,说明slf4j使用的是门面模式(Facade),然后我们就很容易猜测到大致的调用过程是,slf4j是通过自己的api去调用实现组件的api,这样来完成适配的。我们重点看看是怎么做到适配的。
源码基于slf4j-api.1.7.25
slf4j通用门面的实现
调用slf4j时我们都是使用它的api,首先我们需要获取它的logger
一般大家使用slf4j都是这样子的
1 | import org.slf4j.Logger; |
getLogger
我们对getLogger()
方法源码跟踪下去
1 | public static Logger getLogger(Class<?> clazz) { |
从ILoggerFactory的名字上来看,这是一个接口,而它又可以生成到具体实际的logger,那我们应该猜测到这个ILoggerFactory会跟其他日志实现相关,但是例如log4j,自己的实现肯定不会关心slf4j的呀,所以应该由适配jar包,即slf4j-log4j12.jar来实现。
继续看代码
1 | public static ILoggerFactory getILoggerFactory() { |
bind
performInitialization()
方法看来是重点
1 | private final static void performInitialization() { |
bind()
方法
1 | private final static void bind() { |
StaticLoggerBinder类
findPossibleStaticLoggerBinderPathSet()
方法
从hard code看重要性,org/slf4j/impl/StaticLoggerBinder.class
就是slf4j日志适配的关键
1 | //hard code |
从类加载器的用法说明,org/slf4j/impl/StaticLoggerBinder.class
要跟slf4j-api.jar包在同一个类加载器中,一般来说即要求放在同一路径下比较稳妥,当然也可以通过-classpath
来指定。
前面我们已经猜测org/slf4j/impl/StaticLoggerBinder
应该是由各种适配器来实现的,我们来看看
在IDE的类搜索,可以找到两个StaticLoggerBinder
调试刚刚的源码,可以看到找到了两个StaticLoggerBinder.class文件
那是因为我本机依赖了
1 | <dependency> |
所以只是看到logback和log4j的适配器包。slf4j是对每一种日志实现都有对应的一个适配实现。适配器包的具体内容我们等下再看。(PS:这不是一个好的依赖配置,等下会说)
到这里我们已经找到了StaticLoggerBinder
类了,StaticLoggerBinder是由各自的slf4j适配器包提供的。
这里有个trick,既然StaticLoggerBinder在slf4j-api有,也在其他logback-classic或slf4j-log4j12有,那么怎么确保JVM只加载到适配器包中的StaticLoggerBinder?其实看看slf4j代码的pom.xml就发现,答案是打包时是没有StaticLoggerBinder打进去的,这样slf4j-api.jar包是没有StaticLoggerBinder类的,JVM在找类时只会找到其他jar包的StaticLoggerBinder。
我们刚刚的源码到bind()
方法的这一句
1 | StaticLoggerBinder.getSingleton(); |
这一句其实已经是调用适配包的代码,我们将会看看logback和log4j对应StaticLoggerBinder类的代码。
对logback适配实现
从上面的依赖我们看出,为什么slf4j对logback的适配是在logback-classic.jar
包呢?logback-classic应该是logback的核心包才对,不应该关心slf4j的。那是因为slf4j和logback是同一个作者,所以才说logback是天然集成slf4j的。
我们看看logback-classic.jar中的StaticLoggerBinder
1 | static { |
上面的就是logback的初始化了。
1 | public ILoggerFactory getLoggerFactory() { |
getLoggerFactory()
方法会返回logback的LoggerContext,而LoggerContext是继承slf4j的ILoggerFactory的,这样就适配到slf4j。
Logger是从LoggerFactory取出的。
看看LoggerContext的getLogger()
方法
1 | public final Logger getLogger(final Class<?> clazz) { |
这里涉及了logback很多逻辑,我们不太需要理会。这里主要看logback的Logger其实是继承了slf4j的Logger,这样就适配到slf4j。
对log4j配置实现
看了logback的适配,就猜到log4j的也差不多
slf4j-log4j12的StaticLoggerBinder
1 | private StaticLoggerBinder() { |
Log4jLoggerFactory()
是继承了slf4j的ILoggerFactory
。继续看getLogger方法。
1 | public Logger getLogger(String name) { |
这里又是把log4j的Logger包装成slf4j的Logger,适配到slf4j。
图解
画了个图总结一下上面代码说的类关系,大家感受一下。
总结
slf4j的适配原理是通过适配包的org/slf4j/impl/StaticLoggerBinder
来做转承,适配包通过继承和使用slf4j-api
的ILoggerFactory
和Logger
来完成适配。
在最新的版本(我看的是1.8.0)已经改为使用Java的SPI机制来实现,StaticLoggerBinder类已经不用了,改为SLF4JServiceProvider,这样就真正的面向接口编程了,不用打包时忽略StaticLoggerBinder。