簡單回顧門面模式html
slf4j是門面模式的典型應用,所以在講slf4j前,咱們先簡單回顧一下門面模式,java
門面模式,其核心爲外部與一個子系統的通訊必須經過一個統一的外觀對象進行,使得子系統更易於使用。用一張圖來表示門面模式的結構爲:android
門面模式的核心爲Facade即門面對象,門面對象核心爲幾個點:apache
大體上來看,對門面模式的回顧到這裏就能夠了,開始接下來對SLF4J的學習。api
咱們爲何要使用slf4japp
咱們爲何要使用slf4j,舉個例子:框架
咱們本身的系統中使用了logback這個日誌系統 咱們的系統使用了A.jar,A.jar中使用的日誌系統爲log4j 咱們的系統又使用了B.jar,B.jar中使用的日誌系統爲slf4j-simple 這樣,咱們的系統就不得不一樣時支持並維護logback、log4j、slf4j-simple三種日誌框架,很是不便。
解決這個問題的方式就是引入一個適配層,由適配層決定使用哪種日誌系統,而調用端只須要作的事情就是打印日誌而不須要關心如何打印日誌,slf4j或者commons-logging就是這種適配層,slf4j是本文研究的對象。maven
從上面的描述,咱們必須清楚地知道一點:slf4j只是一個日誌標準,並非日誌系統的具體實現。理解這句話很是重要,slf4j只作兩件事情:學習
slf4j-simple、logback都是slf4j的具體實現,log4j並不直接實現slf4j,可是有專門的一層橋接slf4j-log4j12來實現slf4j。ui
爲了更理解slf4j,咱們先看例子,再讀源碼,相信讀者朋友會對slf4j有更深入的認識。
slf4j應用舉例
上面講了,slf4j的直接/間接實現有slf4j-simple、logback、slf4j-log4j12,咱們先定義一個pom.xml,引入相關jar包:
1 <!-- 原文:五月的倉頡http://www.cnblogs.com/xrq730/p/8619156.html --> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 6 <groupId>org.xrq.log</groupId> 7 <artifactId>log-test</artifactId> 8 <version>1.0.0</version> 9 <packaging>jar</packaging> 10 11 <name>log-test</name> 12 <url>http://maven.apache.org</url> 13 14 <properties> 15 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 16 </properties> 17 18 <dependencies> 19 <dependency> 20 <groupId>junit</groupId> 21 <artifactId>junit</artifactId> 22 <version>4.11</version> 23 <scope>test</scope> 24 </dependency> 25 <dependency> 26 <groupId>org.slf4j</groupId> 27 <artifactId>slf4j-api</artifactId> 28 <version>1.7.25</version> 29 </dependency> 30 <dependency> 31 <groupId>ch.qos.logback</groupId> 32 <artifactId>logback-classic</artifactId> 33 <version>1.2.3</version> 34 </dependency> 35 <dependency> 36 <groupId>org.slf4j</groupId> 37 <artifactId>slf4j-simple</artifactId> 38 <version>1.7.25</version> 39 </dependency> 40 <dependency> 41 <groupId>log4j</groupId> 42 <artifactId>log4j</artifactId> 43 <version>1.2.17</version> 44 </dependency> 45 <dependency> 46 <groupId>org.slf4j</groupId> 47 <artifactId>slf4j-log4j12</artifactId> 48 <version>1.7.21</version> 49 </dependency> 50 </dependencies> 51 </project>
寫一段簡單的Java代碼:
1 @Test 2 public void testSlf4j() { 3 Logger logger = LoggerFactory.getLogger(Object.class); 4 logger.error("123"); 5 }
接着咱們首先把上面pom.xml的第30行~第49行註釋掉,即不引入任何slf4j的實現類,運行Test方法,咱們看一下控制檯的輸出爲:
看到沒有任何日誌的輸出,這驗證了咱們的觀點:slf4j不提供日誌的具體實現,只有slf4j是沒法打印日誌的。
接着打開logback-classic的註釋,運行Test方法,咱們看一下控制檯的輸出爲:
看到咱們只要引入了一個slf4j的具體實現類,便可使用該日誌框架輸出日誌。
最後作一個測驗,咱們把全部日誌打開,引入logback-classic、slf4j-simple、log4j,運行Test方法,控制檯輸出爲:
和上面的差異是,能夠輸出日誌,可是會輸出一些告警日誌,提示咱們同時引入了多個slf4j的實現,而後選擇其中的一個做爲咱們使用的日誌系統。
從例子咱們能夠得出一個重要的結論,即slf4j的做用:只要全部代碼都使用門面對象slf4j,咱們就不須要關心其具體實現,最終全部地方使用一種具體實現便可,更換、維護都很是方便。
slf4j實現原理
上面看了slf4j的示例,下面研究一下slf4j的實現,咱們只關注重點代碼。
slf4j的用法就是常年不變的一句"Logger logger = LoggerFactory.getLogger(Object.class);",可見這裏就是經過LoggerFactory去拿slf4j提供的一個Logger接口的具體實現而已,LoggerFactory的getLogger的方法實現爲:
1 public static Logger getLogger(Class<?> clazz) { 2 Logger logger = getLogger(clazz.getName()); 3 if (DETECT_LOGGER_NAME_MISMATCH) { 4 Class<?> autoComputedCallingClass = Util.getCallingClass(); 5 if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 6 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 7 autoComputedCallingClass.getName())); 8 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 9 } 10 } 11 return logger; 12 }
從第2行開始跟代碼,一直跟到LoggerFactory的bind()方法:
1 private final static void bind() { 2 try { 3 Set<URL> staticLoggerBinderPathSet = null; 4 // skip check under android, see also 5 // http://jira.qos.ch/browse/SLF4J-328 6 if (!isAndroid()) { 7 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 8 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); 9 } 10 // the next line does the binding 11 StaticLoggerBinder.getSingleton(); 12 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 13 reportActualBinding(staticLoggerBinderPathSet); 14 fixSubstituteLoggers(); 15 replayEvents(); 16 // release all resources in SUBST_FACTORY 17 SUBST_FACTORY.clear(); 18 } catch (NoClassDefFoundError ncde) { 19 String msg = ncde.getMessage(); 20 if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { 21 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; 22 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); 23 Util.report("Defaulting to no-operation (NOP) logger implementation"); 24 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); 25 } else { 26 failedBinding(ncde); 27 throw ncde; 28 } 29 } catch (java.lang.NoSuchMethodError nsme) { 30 String msg = nsme.getMessage(); 31 if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) { 32 INITIALIZATION_STATE = FAILED_INITIALIZATION; 33 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); 34 Util.report("Your binding is version 1.5.5 or earlier."); 35 Util.report("Upgrade your binding to version 1.6.x."); 36 } 37 throw nsme; 38 } catch (Exception e) { 39 failedBinding(e); 40 throw new IllegalStateException("Unexpected initialization failure", e); 41 } 42 }
這個地方第7行是一個關鍵,看一下代碼:
1 static Set<URL> findPossibleStaticLoggerBinderPathSet() { 2 // use Set instead of list in order to deal with bug #138 3 // LinkedHashSet appropriate here because it preserves insertion order 4 // during iteration 5 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); 6 try { 7 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); 8 Enumeration<URL> paths; 9 if (loggerFactoryClassLoader == null) { 10 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 11 } else { 12 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 13 } 14 while (paths.hasMoreElements()) { 15 URL path = paths.nextElement(); 16 staticLoggerBinderPathSet.add(path); 17 } 18 } catch (IOException ioe) { 19 Util.report("Error getting resources from path", ioe); 20 } 21 return staticLoggerBinderPathSet; 22 }
這個地方重點其實就是第12行的代碼,getLogger的時候會去classpath下找STATIC_LOGGER_BINDER_PATH,STATIC_LOGGER_BINDER_PATH值爲"org/slf4j/impl/StaticLoggerBinder.class",即全部slf4j的實現,在提供的jar包路徑下,必定是有"org/slf4j/impl/StaticLoggerBinder.class"存在的,咱們能夠看一下:
咱們不能避免在系統中同時引入多個slf4j的實現,因此接收的地方是一個Set。你們應該注意到,上部分在演示同時引入logback、slf4j-simple、log4j的時候會有警告:
這就是由於有三個"org/slf4j/impl/StaticLoggerBinder.class"存在的緣由,此時reportMultipleBindingAmbiguity方法控制檯輸出語句:
1 private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) { 2 if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { 3 Util.report("Class path contains multiple SLF4J bindings."); 4 for (URL path : binderPathSet) { 5 Util.report("Found binding in [" + path + "]"); 6 } 7 Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); 8 } 9 }
那網友朋友可能會問,同時存在三個"org/slf4j/impl/StaticLoggerBinder.class"怎麼辦?首先肯定的是這不會致使啓動報錯,其次在這種狀況下編譯期間,編譯器會選擇其中一個StaticLoggerBinder.class進行綁定,這個地方sfl4j也在reportActualBinding方法中報告了綁定的是哪一個日誌框架:
1 private static void reportActualBinding(Set<URL> binderPathSet) { 2 // binderPathSet can be null under Android 3 if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { 4 Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]"); 5 } 6 }
對照上面的截圖,看最後一行,確實是"Actual binding is of type..."這句。
最後StaticLoggerBinder就比較簡單了,不一樣的StaticLoggerBinder其getLoggerFactory實現不一樣,拿到ILoggerFactory以後調用一下getLogger即拿到了具體的Logger,可使用Logger進行日誌輸出。