提出问题
阅读源码之前, 首先提几个问题
SLF4J 是如何整合不同的日志框架的
Class Path 中为什么只能有且仅有一种日志框架的 binding
这段文字摘录自官网: In your code, in addition to slf4j-API-1.8.0-beta2.jar, you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path.
源码版本
- org.slf4j:slf4j-API:1.7.25
- org.apache.logging.log4j:log4j-slf4j-impl:2.11.1
- ch.qos.logback:logback-classic:1.2.3
- org.slf4j:slf4j-jcl:1.7.25
源码解析
带着上面的两个问题看下源码
bind() 方法中通过调用 findPossibleStaticLoggerBinderPathSet() 方法来查找日志框架的绑定
- // org/slf4j/slf4j-API/1.7.25/slf4j-API-1.7.25.jar!/org/slf4j/LoggerFactory.class
- private static final void bind() {
- // ...
- if (!isAndroid()) {
- // 查找日志框架的绑定
- staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
- reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
- }
- // ...
- }
findPossibleStaticLoggerBinderPathSet() 方法中通过 ClassLoader 或者 loggerFactoryClassLoader 来获取名为 "STATIC_LOGGER_BINDER_PATH" 的 Resources "STATIC_LOGGER_BINDER_PATH" 的值在文件一开始已经定义过了
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
- // org/slf4j/slf4j-API/1.7.25/slf4j-API-1.7.25.jar!/org/slf4j/LoggerFactory.class
- static Set<URL> findPossibleStaticLoggerBinderPathSet() {
- LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
- try {
- ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
- Enumeration paths;
- // 通过 ClassLoader 或者 loggerFactoryClassLoader 来获取 Resources
- if (loggerFactoryClassLoader == null) {
- paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
- } else {
- paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
- }
- while(paths.hasMoreElements()) {
- URL path = (URL)paths.nextElement();
- staticLoggerBinderPathSet.add(path);
- }
- } catch (IOException var4) {
- Util.report("Error getting resources from path", var4);
- }
- return staticLoggerBinderPathSet;
- }
这里拿整合 Log4j 举例,"org/slf4j/impl/StaticLoggerBinder.class" 就在 log4j 的 log4j-slf4j-impl 库中, 这个类负责初始化 Log4j 相关的类. StaticLoggerBinder 实现了 LoggerFactoryBinder 接口.
- // org/apache/logging/log4j/log4j-slf4j-impl/2.11.1/log4j-slf4j-impl-2.11.1.jar!/org/slf4j/impl/StaticLoggerBinder.class
- // 这里的包名定义为 org.slf4j.impl, 使得上述第 2 步的 Class Loader 可以加载到这个类
- package org.slf4j.impl;
- public final class StaticLoggerBinder implements LoggerFactoryBinder {
- // log4j init...
- }
其他日志框架也定义了 StaticLoggerBinder 类, 且实现了 LoggerFactoryBinder 接口. 例如: logback,jcl
- // ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class
- // 这里的包名定义为 org.slf4j.impl, 使得上述第 2 步的 Class Loader 可以加载到这个类
- package org.slf4j.impl;
- public class StaticLoggerBinder implements LoggerFactoryBinder {
- // logback init...
- }
- // org/slf4j/slf4j-jcl/1.7.25/slf4j-jcl-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class
- // 这里的包名定义为 org.slf4j.impl, 使得上述第 2 步的 Class Loader 可以加载到这个类
- package org.slf4j.impl;
- public class StaticLoggerBinder implements LoggerFactoryBinder {
- // jcl init...
- }
问题答案
SLF4J 是如何整合不同的日志框架的 SLF4J 通过加载各个底层日志框架桥接库的 org/slf4j/impl/StaticLoggerBinder.class 来加载初始化对应的日志框架.
Class Path 中为什么只能有且仅有一种日志框架的 binding slf4j-API 中的 LoggerFactory 中, 在上述第 2 步返回 staticLoggerBinderPathSet 后, 会立马调用 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet) 方法来检测是否有多个 binding. 如果有多个 binding, 就输出错误信息 "Class path contains multiple SLF4J bindings."
- private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
- if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
- Util.report("Class path contains multiple SLF4J bindings.");
- Iterator i$ = binderPathSet.iterator();
- while(i$.hasNext()) {
- URL path = (URL)i$.next();
- Util.report("Found binding in [" + path + "]");
- }
- Util.report("See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.");
- }
- }
最后
代码中使用 SLF4J 来记录日志, 可以任意切换底层日志框架而不需要修改代码, 只需要更新依赖以及日志配置文件即可.
来源: https://juejin.im/post/5c13a6686fb9a04a027a3e2a