一、导读
我们使用log4j框架时,经常会用slf4j-api。在运行时,经常会遇到如下的错误提示:
1
2
3
4
5
|
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar: file : /Users/abc/maven-repository/org/slf4j/slf4j-simple/1 .7.26 /slf4j-simple-1 .7.26.jar! /org/slf4j/impl/StaticLoggerBinder .class] SLF4J: Found binding in [jar: file : /Users/abc/maven-repository/org/apache/logging/log4j/log4j-slf4j-impl/2 .7 /log4j-slf4j-impl-2 .7.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 [org.slf4j.impl.SimpleLoggerFactory] |
也即classpath下有多个日志实现,slf4j-api在绑定时出现了冲突。那么这时slf4j-api到底会绑定哪一个具体的实现呢?
官方文档有如下说明:
也就是说,当有多个日志实现时,SLF4J会在编译期随机选择其中的一个实现。那么,它到底是如何随机选择的呢?下面我们通过源码来分析。
请尊重作者劳动成果,转载请标明原文链接:https://www.cnblogs.com/waterystone/p/11329645.html
二、源码分析
2.1 LoggerFactory.getLogger(this.getClass())
我们通过LoggerFactory.getLogger(this.getClass())拿到Logger对象,其代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
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.2 LoggerFactory.bind()
getLogger() -> getILoggerFactory()-> performInitialization() -> bind() ,bind()即绑定日志实现的函数,其源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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(); //在classpath下查找有多少个日志实现 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); //如果有多个日志实现,打印出来 } // the next line does the binding。classpath下每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类,这里会随机加载其中的一个。 StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); fixSubstituteLoggers(); replayEvents(); // release all resources in SUBST_FACTORY SUBST_FACTORY.clear(); } catch (NoClassDefFoundError ncde) { //如果在classpath下没有找到一个org.slf4j.impl.StaticLoggerBinder 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); } } |
2.3 LoggerFactory.findPossibleStaticLoggerBinderPathSet()
接下来,我们再看看findPossibleStaticLoggerBinderPathSet()如何在classpath下查找有多少个日志实现的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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 ) { //用ClassLoader去查找classpath下有多少个org.slf4j.impl.StaticLoggerBinder类 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; } |
通过源码我们可以看到,findPossibleStaticLoggerBinderPathSet()会用ClassLoader查找当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,每个path的URL能定位到其具体jar包位置。每个日志实现jar包都会有个org.slf4j.impl.StaticLoggerBinder类,反过来,那么classpath下有多少个StaticLoggerBinder类,就会有多少个相应的jar包,也即有多少个日志实现。所以findPossibleStaticLoggerBinderPathSet()通过扫描classpath下的org.slf4j.impl.StaticLoggerBinder类就能找到有多少个日志实现。
接下来,我们再看看是不是每个日志实现jar包都会有个org.slf4j.impl.StaticLoggerBinder类呢?答案是YES。
三、具体日志实现
3.1 slf4j-simple
3.2 slf4j-nop
3.3 logback-classic
3.4 log4j-slf4j-impl
四、总结
- 每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类的实现;
- SLF4J会通过ClassLoad扫描当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,也就找到了有多少个日志实现(通过这样,我们只需在项目中加入日志实现的jar包,编译时即可自动加载,业务代码无须显式依赖,实现解耦);
- 如果有多个org.slf4j.impl.StaticLoggerBinder类,SLF4J会在LoggerFactory.bind()里调用StaticLoggerBinder.getSingleton()随机加载一个日志实现jar的StaticLoggerBinder。
- 发现一个有趣的现象,在本地idea上运行时,是加载pom.xml里声明的第一个日志实现;但是如果打包好后通过java -jar启动时,其加载的日志实现确实是随机的(是在编译打包时随机加载一个日志实现,所以一旦编译打包好后其加载的那个日志实现就会固定不变)。所以,我们在具体使用时一定要通过排除依赖的方式来确定日志实现,不要由于日志实现的不确定性引入难以排查、不必要的坑。
- 作者:水岩
- 出处:http://www.cnblogs.com/waterystone
- 本博客中未标明转载的文章归作者水岩和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果您觉得本文对您的学习有所帮助,可通过支付宝(左) 或者 微信(右) 来打赏博主,增加博主的写作动力