[java进阶]3.slf4j作用及其实现原理
參考博客:https://www.cnblogs.com/xrq730/p/8619156.html
1.?簡(jiǎn)單回顧門(mén)面模式
slf4j是門(mén)面模式的典型應(yīng)用,因此在講slf4j前,我們先簡(jiǎn)單回顧一下門(mén)面模式,門(mén)面模式,其核心為外部與一個(gè)子系統(tǒng)的通信必須通過(guò)一個(gè)統(tǒng)一的外觀對(duì)象進(jìn)行,使得子系統(tǒng)更易于使用。用一張圖來(lái)表示門(mén)面模式的結(jié)構(gòu)為:
門(mén)面模式的核心為Facade即門(mén)面對(duì)象,門(mén)面對(duì)象核心為幾個(gè)點(diǎn):
- 知道所有子角色的功能和責(zé)任
- 將客戶端發(fā)來(lái)的請(qǐng)求委派到子系統(tǒng)中,沒(méi)有實(shí)際業(yè)務(wù)邏輯
- 不參與子系統(tǒng)內(nèi)業(yè)務(wù)邏輯的實(shí)現(xiàn)
2.?我們?yōu)槭裁匆褂胹lf4j
我們?yōu)槭裁匆褂胹lf4j,舉個(gè)例子:
我們自己的系統(tǒng)中使用了logback這個(gè)日志系統(tǒng) 我們的系統(tǒng)使用了A.jar,A.jar中使用的日志系統(tǒng)為log4j 我們的系統(tǒng)又使用了B.jar,B.jar中使用的日志系統(tǒng)為slf4j-simple這樣,我們的系統(tǒng)就不得不同時(shí)支持并維護(hù)logback、log4j、slf4j-simple三種日志框架,非常不便。
解決這個(gè)問(wèn)題的方式就是引入一個(gè)適配層,由適配層決定使用哪一種日志系統(tǒng),而調(diào)用端只需要做的事情就是打印日志而不需要關(guān)心如何打印日志,slf4j或者commons-logging就是這種適配層,slf4j是本文研究的對(duì)象。
從上面的描述,我們必須清楚地知道一點(diǎn):slf4j只是一個(gè)日志標(biāo)準(zhǔn),并不是日志系統(tǒng)的具體實(shí)現(xiàn)。理解這句話非常重要,slf4j只做兩件事情:
slf4j-simple、logback都是slf4j的具體實(shí)現(xiàn),log4j并不直接實(shí)現(xiàn)slf4j,但是有專(zhuān)門(mén)的一層橋接slf4j-log4j12來(lái)實(shí)現(xiàn)slf4j。為了更理解slf4j,我們先看例子,再讀源碼,相信讀者朋友會(huì)對(duì)slf4j有更深刻的認(rèn)識(shí)。
- slf4j應(yīng)用舉例(moven引入多個(gè)日志實(shí)現(xiàn))
測(cè)試代碼:
public void testSlf4j() {Logger logger = LoggerFactory.getLogger(Object.class);logger.error("123"); }接著我們首先把上面pom.xml的log實(shí)現(xiàn)方法注釋掉,即不引入任何slf4j的實(shí)現(xiàn)類(lèi),運(yùn)行Test方法,這時(shí)候會(huì)報(bào)錯(cuò),找不到打印log的實(shí)現(xiàn)方法:
看到?jīng)]有任何日志的輸出,這驗(yàn)證了我們的觀點(diǎn):slf4j不提供日志的具體實(shí)現(xiàn),只有slf4j是無(wú)法打印日志的。
接著,可以把所有的日志實(shí)現(xiàn)方法都打開(kāi),測(cè)試結(jié)果:
和上面的差別是,可以輸出日志,但是會(huì)輸出一些告警日志,提示我們同時(shí)引入了多個(gè)slf4j的實(shí)現(xiàn),然后選擇其中的一個(gè)作為我們使用的日志系統(tǒng)。從例子我們可以得出一個(gè)重要的結(jié)論,即slf4j的作用:只要所有代碼都使用門(mén)面對(duì)象slf4j,我們就不需要關(guān)心其具體實(shí)現(xiàn),最終所有地方使用一種具體實(shí)現(xiàn)即可,更換、維護(hù)都非常方便。
- slf4j實(shí)現(xiàn)原理
slf4j的用法就是常年不變的一句"Logger logger = LoggerFactory.getLogger(Object.class);",可見(jiàn)這里就是通過(guò)LoggerFactory去拿slf4j提供的一個(gè)Logger接口的具體實(shí)現(xiàn)而已,LoggerFactory的getLogger的方法實(shí)現(xiàn)為:
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行開(kāi)始跟代碼,一直跟到LoggerFactory的bind()方法:
private final static void bind() {try {Set<URL> staticLoggerBinderPathSet = null;// skip check under android, see also// http://jira.qos.ch/browse/SLF4J-328if (!isAndroid()) {staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);}// the next line does the bindingStaticLoggerBinder.getSingleton();INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;reportActualBinding(staticLoggerBinderPathSet);fixSubstituteLoggers();replayEvents();// release all resources in SUBST_FACTORYSUBST_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);} }這個(gè)地方第7行是一個(gè)關(guān)鍵,看一下代碼:
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 iterationSet<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; }這個(gè)地方重點(diǎn)其實(shí)就是第12行的代碼,getLogger的時(shí)候會(huì)去classpath下找STATIC_LOGGER_BINDER_PATH,STATIC_LOGGER_BINDER_PATH值為"org/slf4j/impl/StaticLoggerBinder.class",即所有slf4j的實(shí)現(xiàn),在提供的jar包路徑下,一定是有"org/slf4j/impl/StaticLoggerBinder.class"存在的,我們可以看一下:
我們不能避免在系統(tǒng)中同時(shí)引入多個(gè)slf4j的實(shí)現(xiàn),所以接收的地方是一個(gè)Set。大家應(yīng)該注意到,上部分在演示同時(shí)引入logback、slf4j-simple、log4j的時(shí)候會(huì)有多個(gè)日志實(shí)現(xiàn)的警告:
因?yàn)橛腥齻€(gè)"org/slf4j/impl/StaticLoggerBinder.class"存在的原因,此時(shí)reportMultipleBindingAmbiguity方法控制臺(tái)輸出語(yǔ)句:
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.");} }那可能會(huì)問(wèn),同時(shí)存在三個(gè)"org/slf4j/impl/StaticLoggerBinder.class"怎么辦?首先確定的是這不會(huì)導(dǎo)致啟動(dòng)報(bào)錯(cuò),其次在這種情況下編譯期間,編譯器會(huì)選擇其中一個(gè)StaticLoggerBinder.class進(jìn)行綁定,這個(gè)地方sfl4j也在reportActualBinding方法中報(bào)告了綁定的是哪個(gè)日志框架:
private static void reportActualBinding(Set<URL> binderPathSet) {// binderPathSet can be null under Androidif (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");} }對(duì)照上面的截圖,看最后一行,確實(shí)是"Actual binding is of type..."這句。
最后StaticLoggerBinder就比較簡(jiǎn)單了,不同的StaticLoggerBinder其getLoggerFactory實(shí)現(xiàn)不同,拿到ILoggerFactory之后調(diào)用一下getLogger即拿到了具體的Logger,可以使用Logger進(jìn)行日志輸出。
總結(jié)
以上是生活随笔為你收集整理的[java进阶]3.slf4j作用及其实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 千万不要成为这样一个程序员!
- 下一篇: C++函数如何操作堆栈指针esp