<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Slf4j 日志框架適配原理

          共 11426字,需瀏覽 23分鐘

           ·

          2021-10-09 19:04

          概要

          看了?Java 日志體系詳解?后,相信大家對slf4j以及其他日志組件的關系有了一定理解。slf4j只是為日志的輸出提供了統(tǒng)一接口,并沒有具體的實現(xiàn),就好像JDBC一樣。那么,大家會不會好奇slf4j是怎么綁定/適配/橋接到log4j或者logback其他日志實現(xiàn)組件的呢?這篇文章為大家詳細講述。

          適配過程原理

          統(tǒng)計API接口,說明slf4j使用的是門面模式(Facade),然后我們就很容易猜測到大致的調用過程是,slf4j是通過自己的api去調用實現(xiàn)組件的api,這樣來完成適配的。我們重點看看是怎么做到適配的。
          源碼基于slf4j-api.1.7.25

          slf4j通用門面的實現(xiàn)

          調用slf4j時我們都是使用它的api,首先我們需要獲取它的logger
          一般大家使用slf4j都是這樣子的

          import?org.slf4j.Logger;
          import?org.slf4j.LoggerFactory;

          private?Logger?logger?=?LoggerFactory.getLogger(LogTest.class);

          getLogger

          我們對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;
          ??}

          ??public?static?Logger?getLogger(String?name)?{
          ??????//獲取logger的工廠來生成logger
          ??????ILoggerFactory?iLoggerFactory?=?getILoggerFactory();
          ??????return?iLoggerFactory.getLogger(name);
          ??}

          從ILoggerFactory的名字上來看,這是一個接口,而它又可以生成到具體實際的logger,那我們應該猜測到這個ILoggerFactory會跟其他日志實現(xiàn)相關,但是例如log4j,自己的實現(xiàn)肯定不會關心slf4j的呀,所以應該由適配jar包,即slf4j-log4j12.jar來實現(xiàn)。
          繼續(xù)看代碼

          public?static?ILoggerFactory?getILoggerFactory()?{
          ????//從ILoggerFactory的狀態(tài)可以看出,ILoggerFactory只會一次初始化
          ????if?(INITIALIZATION_STATE?==?UNINITIALIZED)?{
          ????????synchronized?(LoggerFactory.class)?{
          ????????????//同步語句?+?雙重判斷,防止多次初始化
          ????????????//如果還沒初始化,則進行初始化
          ????????????if?(INITIALIZATION_STATE?==?UNINITIALIZED)?{
          ????????????????INITIALIZATION_STATE?=?ONGOING_INITIALIZATION;
          ????????????????performInitialization();
          ????????????}
          ????????}
          ????}
          ????switch?(INITIALIZATION_STATE)?{
          ????//初始化成功,即綁定成功,則從StaticLoggerBinder獲取ILoggerFactory并返回
          ????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:
          ????????return?SUBST_FACTORY;
          ????}
          ????throw?new?IllegalStateException("Unreachable?code");
          }

          //對ILoggerFactory的狀態(tài)做說明
          static?final?int?UNINITIALIZED?=?0;?//沒初始化
          static?final?int?ONGOING_INITIALIZATION?=?1;?//正在初始化
          static?final?int?FAILED_INITIALIZATION?=?2;?//初始化失敗
          static?final?int?SUCCESSFUL_INITIALIZATION?=?3;?//初始化成功
          static?final?int?NOP_FALLBACK_INITIALIZATION?=?4;?//無日志實現(xiàn)

          bind

          performInitialization()方法看來是重點

          private?final?static?void?performInitialization()?{
          ????bind();
          ????if?(INITIALIZATION_STATE?==?SUCCESSFUL_INITIALIZATION)?{
          ????????versionSanityCheck();
          ????}
          }

          bind()方法

          private?final?static?void?bind()?{
          ????try?{
          ????????Set?staticLoggerBinderPathSet?=?null;
          ????????if?(!isAndroid())?{
          ????????????//找出可能綁定的日志的path,其實即StaticLoggerBinder.class文件
          ????????????staticLoggerBinderPathSet?=?findPossibleStaticLoggerBinderPathSet();
          ????????????//如果找出多個的話則打印錯誤信息。(等下會演示)
          ????????????reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
          ????????}
          ????????//通過獲取單例來做初始化??????????
          ????????StaticLoggerBinder.getSingleton();
          ????????INITIALIZATION_STATE?=?SUCCESSFUL_INITIALIZATION;
          ????????//打印實際綁定的那個日志實現(xiàn)。(等下會演示)
          ????????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);
          ????}
          }

          StaticLoggerBinder類

          findPossibleStaticLoggerBinderPathSet()方法
          從hard code看重要性,
          org/slf4j/impl/StaticLoggerBinder.class就是slf4j日志適配的關鍵

          //hard?code
          private?static?String?STATIC_LOGGER_BINDER_PATH?=?"org/slf4j/impl/StaticLoggerBinder.class";

          static?Set?findPossibleStaticLoggerBinderPathSet()?{

          ????Set?staticLoggerBinderPathSet?=?new?LinkedHashSet();
          ????try?{
          ????????//獲取LoggerFactory,即slf4j-apoi的類加載器
          ????????ClassLoader?loggerFactoryClassLoader?=?LoggerFactory.class.getClassLoader();
          ????????Enumeration?paths;
          ????????//為null說明是由Bootstrap?Classloader加載的,則轉為App?Classloader去加載
          ????????if?(loggerFactoryClassLoader?==?null)?{
          ????????????paths?=?ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
          ????????}?else?{
          ????????????//用跟slf4j一樣的Classloader去加載
          ????????????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;
          }

          從類加載器的用法說明,org/slf4j/impl/StaticLoggerBinder.class要跟slf4j-api.jar包在同一個類加載器中,一般來說即要求放在同一路徑下比較穩(wěn)妥,當然也可以通過-classpath來指定。

          前面我們已經(jīng)猜測org/slf4j/impl/StaticLoggerBinder應該是由各種適配器來實現(xiàn)的,我們來看看

          在IDE的類搜索,可以找到兩個StaticLoggerBinder

          調試剛剛的源碼,可以看到找到了兩個StaticLoggerBinder.class文件


          那是因為我本機依賴了

          <dependency>
          ????<groupId>ch.qos.logbackgroupId>

          ????<artifactId>logback-classicartifactId>
          ????<version>${logback.version}version>
          dependency>

          <dependency>
          ????<groupId>org.slf4jgroupId>
          ????<artifactId>slf4j-log4j12artifactId>
          ????<version>${slf4j.version}version>
          dependency>

          所以只是看到logback和log4j的適配器包。slf4j是對每一種日志實現(xiàn)都有對應的一個適配實現(xiàn)。適配器包的具體內容我們等下再看。(PS:這不是一個好的依賴配置,等下會說)
          到這里我們已經(jīng)找到了
          StaticLoggerBinder類了,StaticLoggerBinder是由各自的slf4j適配器包提供的。
          這里有個trick,既然StaticLoggerBinder在slf4j-api有,也在其他logback-classic或slf4j-log4j12有,那么怎么確保JVM只加載到適配器包中的StaticLoggerBinder?其實看看slf4j代碼的pom.xml就發(fā)現(xiàn),答案是打包時是沒有StaticLoggerBinder打進去的,這樣slf4j-api.jar包是沒有StaticLoggerBinder類的,JVM在找類時只會找到其他jar包的StaticLoggerBinder。

          我們剛剛的源碼到bind()方法的這一句

          StaticLoggerBinder.getSingleton();

          這一句其實已經(jīng)是調用適配包的代碼,我們將會看看logback和log4j對應StaticLoggerBinder類的代碼。

          對logback適配實現(xiàn)

          從上面的依賴我們看出,為什么slf4j對logback的適配是在logback-classic.jar包呢?logback-classic應該是logback的核心包才對,不應該關心slf4j的。那是因為slf4j和logback是同一個作者,所以才說logback是天然集成slf4j的。

          我們看看logback-classic.jar中的StaticLoggerBinder

          static?{
          ????SINGLETON.init();
          }
          public?static?StaticLoggerBinder?getSingleton()?{
          ????return?SINGLETON;
          }
          void?init()?{
          ????try?{
          ????????try?{
          ????????????new?ContextInitializer(defaultLoggerContext).autoConfig();
          ????????}?catch?(JoranException?je)?{
          ????????????Util.report("Failed?to?auto?configure?default?logger?context",?je);
          ????????}
          ????????//?logback-292
          ????????if?(!StatusUtil.contextHasStatusListener(defaultLoggerContext))?{
          ????????????StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
          ????????}
          ????????contextSelectorBinder.init(defaultLoggerContext,?KEY);
          ????????initialized?=?true;
          ????}?catch?(Exception?t)?{?//?see?LOGBACK-1159
          ????????Util.report("Failed?to?instantiate?["?+?LoggerContext.class.getName()?+?"]",?t);
          ????}
          }

          上面的就是logback的初始化了。

          public?ILoggerFactory?getLoggerFactory()?{
          ????if?(!initialized)?{
          ????????return?defaultLoggerContext;
          ????}

          ????if?(contextSelectorBinder.getContextSelector()?==?null)?{
          ????????throw?new?IllegalStateException("contextSelector?cannot?be?null.?See?also?"?+?NULL_CS_URL);
          ????}
          ????return?contextSelectorBinder.getContextSelector().getLoggerContext();
          }

          getLoggerFactory()方法會返回logback的LoggerContext,而LoggerContext是繼承slf4j的ILoggerFactory的,這樣就適配到slf4j。

          Logger是從LoggerFactory取出的。
          看看LoggerContext的
          getLogger()方法

          public?final?Logger?getLogger(final?Class?clazz)?{
          ????return?getLogger(clazz.getName());
          }

          @Override
          public?final?Logger?getLogger(final?String?name)?{

          ????if?(name?==?null)?{
          ????????throw?new?IllegalArgumentException("name?argument?cannot?be?null");
          ????}

          ????//?if?we?are?asking?for?the?root?logger,?then?let?us?return?it?without
          ????//?wasting?time
          ????if?(Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name))?{
          ????????return?root;
          ????}

          ????int?i?=?0;
          ????Logger?logger?=?root;

          ????//?check?if?the?desired?logger?exists,?if?it?does,?return?it
          ????//?without?further?ado.
          ????Logger?childLogger?=?(Logger)?loggerCache.get(name);
          ????//?if?we?have?the?child,?then?let?us?return?it?without?wasting?time
          ????if?(childLogger?!=?null)?{
          ????????return?childLogger;
          ????}

          ????//?if?the?desired?logger?does?not?exist,?them?create?all?the?loggers
          ????//?in?between?as?well?(if?they?don't?already?exist)
          ????String?childName;
          ????while?(true)?{
          ????????int?h?=?LoggerNameUtil.getSeparatorIndexOf(name,?i);
          ????????if?(h?==?-1)?{
          ????????????childName?=?name;
          ????????}?else?{
          ????????????childName?=?name.substring(0,?h);
          ????????}
          ????????//?move?i?left?of?the?last?point
          ????????i?=?h?+?1;
          ????????synchronized?(logger)?{
          ????????????childLogger?=?logger.getChildByName(childName);
          ????????????if?(childLogger?==?null)?{
          ????????????????childLogger?=?logger.createChildByName(childName);
          ????????????????loggerCache.put(childName,?childLogger);
          ????????????????incSize();
          ????????????}
          ????????}
          ????????logger?=?childLogger;
          ????????if?(h?==?-1)?{
          ????????????return?childLogger;
          ????????}
          ????}
          }

          這里涉及了logback很多邏輯,我們不太需要理會。這里主要看logback的Logger其實是繼承了slf4j的Logger,這樣就適配到slf4j。

          對log4j配置實現(xiàn)

          看了logback的適配,就猜到log4j的也差不多
          slf4j-log4j12的StaticLoggerBinder

          private?StaticLoggerBinder()?{
          ??????loggerFactory?=?new?Log4jLoggerFactory();
          ??????try?{
          ??????????@SuppressWarnings("unused")
          ??????????Level?level?=?Level.TRACE;
          ??????}?catch?(NoSuchFieldError?nsfe)?{
          ??????????Util.report("This?version?of?SLF4J?requires?log4j?version?1.2.12?or?later.?See?also?http://www.slf4j.org/codes.html#log4j_version");
          ??????}
          ??}

          ??public?ILoggerFactory?getLoggerFactory()?{
          ??????return?loggerFactory;
          ??}

          Log4jLoggerFactory()是繼承了slf4j的ILoggerFactory。繼續(xù)看getLogger方法。

          public?Logger?getLogger(String?name)?{
          ????Logger?slf4jLogger?=?loggerMap.get(name);
          ????if?(slf4jLogger?!=?null)?{
          ????????return?slf4jLogger;
          ????}?else?{
          ????????org.apache.log4j.Logger?log4jLogger;
          ????????if?(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
          ????????????log4jLogger?=?LogManager.getRootLogger();
          ????????else
          ????????????log4jLogger?=?LogManager.getLogger(name);

          ????????Logger?newInstance?=?new?Log4jLoggerAdapter(log4jLogger);
          ????????Logger?oldInstance?=?loggerMap.putIfAbsent(name,?newInstance);
          ????????return?oldInstance?==?null???newInstance?:?oldInstance;
          ????}
          }

          這里又是把log4j的Logger包裝成slf4j的Logger,適配到slf4j。

          圖解

          畫了個圖總結一下上面代碼說的類關系,大家感受一下。

          總結

          slf4j的適配原理是通過適配包的org/slf4j/impl/StaticLoggerBinder來做轉承,適配包通過繼承和使用slf4j-apiILoggerFactoryLogger來完成適配。
          在最新的版本(我看的是1.8.0)已經(jīng)改為使用Java的SPI機制來實現(xiàn),StaticLoggerBinder類已經(jīng)不用了,改為SLF4JServiceProvider,這樣就真正的面向接口編程了,不用打包時忽略StaticLoggerBinder。

          source:?//albenw.github.io/posts/e31dfd0e/

          喜歡,在看

          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91福利视频在线观看 | 无码免费视频观看 | 成人免费精品视频 | 成人精品在线视频 | 草草在线视频 |