SLF4J是一個日志框架抽象層,底下綁定具體的日志框架,比如說Log4J,Logback,Java Logging API等。SLF4J也有自身的默認實現,但是我們還是主要以日志框架抽象層的身份使用SLF4J。
要使用SLF4J,得包含對"org.slf4j:slf4j-api"的依賴。
簡單回顧門面模式
slf4j是門面模式的典型應用,因此在講slf4j前,我們先簡單回顧一下門面模式,
門面模式,其核心為外部與一個子系統的通信必須通過一個統一的外觀對象進行,使得子系統更易于使用。用一張圖來表示門面模式的結構為:
門面模式的核心為Facade即門面對象,門面對象核心為幾個點:
大致上來看,對門面模式的回顧到這里就可以了,開始接下來對SLF4J的學習。
我們為什么要使用slf4j
我們為什么要使用slf4j,舉個例子:
我們自己的系統中使用了logback這個日志系統
我們的系統使用了A.jar,A.jar中使用的日志系統為log4j
我們的系統又使用了B.jar,B.jar中使用的日志系統為slf4j-simple
這樣,我們的系統就不得不同時支持并維護logback、log4j、slf4j-simple三種日志框架,非常不便。
解決這個問題的方式就是引入一個適配層,由適配層決定使用哪一種日志系統,而調用端只需要做的事情就是打印日志而不需要關心如何打印日志,slf4j或者commons-logging就是這種適配層,slf4j是本文研究的對象。
從上面的描述,我們必須清楚地知道一點:slf4j只是一個日志標準,并不是日志系統的具體實現。理解這句話非常重要,slf4j只提做兩件事情:
slf4j-simple、logback都是slf4j的具體實現,log4j并不直接實現slf4j,但是有專門的一層橋接slf4j-log4j12來實現slf4j。
為了更理解slf4j,我們先看例子,再讀源碼,相信讀者朋友會對slf4j有更深刻的認識。
slf4j應用舉例
上面講了,slf4j的直接/間接實現有slf4j-simple、logback、slf4j-log4j12,我們先定義一個pom.xml,引入相關jar包:
<!-- 原文:五月的倉頡http://www.cnblogs.com/xrq730/p/8619156.html --><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.xrq.log</groupId> <artifactId>log-test</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>log-test</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> </dependencies></project>
寫一段簡單的Java代碼:
@Testpublic void testSlf4j() { Logger logger = LoggerFactory.getLogger(Object.class); logger.error("123"); }
接著我們首先把上面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的方法實現為:
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行開始跟代碼,一直跟到LoggerFactory的bind()方法:
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(); 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) { 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); }}
這個地方第7行是一個關鍵,看一下代碼:
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) { 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;}
這個地方重點其實就是第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方法控制臺輸出語句:
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."); }}
那網友朋友可能會問,同時存在三個"org/slf4j/impl/StaticLoggerBinder.class"怎么辦?首先確定的是這不會導致啟動報錯,其次在這種情況下編譯期間,編譯器會選擇其中一個StaticLoggerBinder.class進行綁定。
最后StaticLoggerBinder就比較簡單了,不同的StaticLoggerBinder其getLoggerFactory實現不同,拿到ILoggerFactory之后調用一下getLogger即拿到了具體的Logger,可以使用Logger進行日志輸出。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。
新聞熱點
疑難解答
圖片精選