读懂 Slf4j、Log4j2、LogBack 关系
我们可以看到,很多文章都在告诉我,Slf4j 是日志的门面,是接口;而 Log4j2 和 Logback 是日志的实现。但是至于它们是怎么联系到一起,我们经常能看到说,使用 log4j-slf4j-impl
建立桥梁,就可以使得 Slf4j 桥接到 Log4j2 上,就可以使用 Log4j 的实现了。而我们的项目中,打日志都直接基于 Slf4j 接口即可。
那么我们思考多个问题:
- 这个绑定操作是怎么实现的呢?
- 如果 Slf4j 绑定了多个日志实现,会怎么样呢?
绑定操作是怎么实现的
首先,我们基本都是如下方式使用 Logger 的。
1 | public static final Logger logger = LoggerFactory.getLogger(LogForOne.class); |
通过查看 LoggerFactory.getLogger
的源代码,最终会找到绑定操作的逻辑。
1 | private final static void performInitialization() { |
我们先看 1.7.x
及之前版本,bind
函数的主要逻辑如下:
1 | Set<URL> staticLoggerBinderPathSet = null; |
可以看到,其核心逻辑是 StaticLoggerBinder
的单例,这儿隐藏了一个含义是——JVM 在类加载的时候如果遇到了同路径同名的 Class 时,则会被忽略。因此可以理解只会拿到唯一一个 StaticLoggerBinder
实例。就是这么简单粗暴。
当然,如果被选中的日志组件依赖不全,则会出错,并不会另外选择依赖完整的日志实现,毕竟它不是智能的。
在 Slf4j 的 2.0 版本有什么差别
区别点只在于 bind
函数的实现。
1 | List<SLF4JServiceProvider> providersList = findServiceProviders(); |
可以之前的可以明显产生对比,这儿已经不存在 StaticLoggerBinder
单例方式的硬连接了。这种方式相比较之前有明显的好吃,要实现一个日志组件,只需要实现接口即可,不需要必须要在日志实现组件中仅仅是为了接入 Slf4j 二引入 StaticLoggerBinder
单例的实现,并且这个实现与日志组件本身是没有意义的。第二点是,不会再存在实现了 StaticLoggerBinder
但没有实现日志接口的情况了。
1 | private static List<SLF4JServiceProvider> findServiceProviders() { |
这儿引入了 SPI 机制,只需要实现了 SLF4JServiceProvider
接口,即可被 Slf4j 所发现,当然这儿也是借助了 Java 所提供的 SPI 机制所实现。
一个日志组件怎么才能支持 Slf4j
接入 Slf4j 1.7.x 及之前版本:
- 实现
Logger
接口 - 实现
ILoggerFactory
接口 - 提供
StaticLoggerBinder
实现,提供单例 - 提供
StaticMarkerBinder
实现,提供单例 - 提供
StaticMDCBinder
实现,提供单例
接入 Slf4j 2.0:
- 实现
Logger
接口 - 实现
ILoggerFactory
接口 - 实现
SLF4JServiceProvider
接口 - 实现
MDCAdapter
接口