一個簡單的spring-boot程序,須要用kafka作消息隊列,因而在maven中引入kafka依賴,一切看似沒問題,在啓動時,打印出Warning信息:html
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
經過警告消息,能夠簡單的看出是slf4j
綁定發生問題,有多個StaticLoggerBinder.class
存在,即slf4j-log4j12
和logback-classic
衝突。java
slf4j-log4j12
依賴,依賴jar
包是被自動引入的,經過maven自帶工具分析依賴路徑,能夠看出是kafka
依賴於slf4j-log4j12
,自動導入的依賴包。 slf4j
的綁定方式。import org.slf4j.Logger; import org.slf4j.LoggerFactory; ··· private final Logger logs = LoggerFactory.getLogger(***.class);
LoggerFactory.getLogger()
方法:(下述均只保留關鍵邏輯代碼 )spring
public static Logger getLogger(Class<?> clazz) { Logger logger = getLogger(clazz.getName()); ··· return logger; } public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory();//看這裏 return iLoggerFactory.getLogger(name);//根據名字返回一個Logger實例對象 }
ILoggerFactory
是一個接口,歸屬package org.slf4j;
僅存在一個方法爲: public Logger getLogger(String name);
接下來就是看看getILoggerFactory()
的真面目:apache
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"); }
能夠看到performInitialization()
是進行初始化的方法:api
private final static void performInitialization() { bind(); ··· }
performInitialization()
內部調用bind()
方法:框架
private final static void bind() { try { Set<URL> staticLoggerBinderPathSet = null; 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) {... } catch (java.lang.NoSuchMethodError nsme) {... } catch (Exception e) {...} }
其中關鍵在於findPossibleStaticLoggerBinderPathSet()
方法,終於到了查找綁定相關的部份內容,能夠看到是查找全部的"org/slf4j/impl/StaticLoggerBinder.class"
類並加載,同時while循環
裏,將可能存在的多個StaticLoggerBinder.class
路徑均加入Set<URL>
返回。maven
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; static Set<URL> findPossibleStaticLoggerBinderPathSet() { 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; }
返回到bind()
方法中:spring-boot
private final static void bind() { ··· staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//看這裏 ··· } ··· private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) { if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { Util.report("Class path contains multiple SLF4J bindings."); for (URL path : binderPathSet) { Util.report("Found binding in [" + path + "]"); } Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); } }
這裏能夠看到reportMultipleBindingAmbiguity()
裏判斷是否發生多重綁定,就是打印文章開頭Warning
信息的地方。 成功加載StaticLoggerBinder
後,在bind()
方法中調用其getSingleton()
方法獲得單例,並修改INITIALIZATION_STATE
狀態,至此完成日誌框架的綁定。工具
private final static void bind() { if (!isAndroid()) { staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } StaticLoggerBinder.getSingleton();//看這裏 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); ··· }
最後附上slf4j-api-1.7.25.jar
和logback-classic-1.2.3.jar
的目錄結構供參考: ui
分析了緣由,那麼解決方案天然很簡單,就是剔除不須要的依賴包,此處就是在kafka
的依賴中剔除slf4j-log4j12
。maven
項目中能夠經過exclusions
標籤來完成。
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>0.10.0.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>
本文就簡單分析了日誌加載綁定的過程,若有遺漏請不吝指出。