SLF4J原始碼解析(一)

CodeNeverStops發表於2018-12-14

提出問題

閱讀原始碼之前,首先提幾個問題

  • SLF4J是如何整合不同的日誌框架的
  • Class Path中為什麼只能有且僅有一種日誌框架的binding

這段文字摘錄自官網:In your code, in addition to slf4j-api-1.8.0-beta2.jar, you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path.

原始碼版本

  • org.slf4j:slf4j-api:1.7.25
  • org.apache.logging.log4j:log4j-slf4j-impl:2.11.1
  • ch.qos.logback:logback-classic:1.2.3
  • org.slf4j:slf4j-jcl:1.7.25

原始碼解析

帶著上面的兩個問題看下原始碼

  1. bind()方法中通過呼叫findPossibleStaticLoggerBinderPathSet()方法來查詢日誌框架的繫結
// org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/org/slf4j/LoggerFactory.class
private static final void bind() {
    // ...
    if (!isAndroid()) {
        // 查詢日誌框架的繫結
        staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
    }
    // ...
}
複製程式碼
  1. findPossibleStaticLoggerBinderPathSet()方法中通過ClassLoader或者loggerFactoryClassLoader來獲取名為"STATIC_LOGGER_BINDER_PATH"的Resources
// org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/org/slf4j/LoggerFactory.class
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();

    try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        Enumeration paths;
        /*
         * 通過ClassLoader或者loggerFactoryClassLoader來獲取Resources
         * "STATIC_LOGGER_BINDER_PATH"的值在檔案一開始已經定義過了
         * private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
         */
        if (loggerFactoryClassLoader == null) {
            paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
        } else {
            paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
        }

        while(paths.hasMoreElements()) {
            URL path = (URL)paths.nextElement();
            staticLoggerBinderPathSet.add(path);
        }
    } catch (IOException var4) {
        Util.report("Error getting resources from path", var4);
    }

    return staticLoggerBinderPathSet;
}
複製程式碼
  1. 這裡拿整合Log4j舉例,"org/slf4j/impl/StaticLoggerBinder.class"就在log4j的log4j-slf4j-impl庫中,這個類負責初始化Log4j相關的類。StaticLoggerBinder實現了LoggerFactoryBinder介面。
// org/apache/logging/log4j/log4j-slf4j-impl/2.11.1/log4j-slf4j-impl-2.11.1.jar!/org/slf4j/impl/StaticLoggerBinder.class

// 這裡的包名定義為org.slf4j.impl,使得上述第2步的Class Loader可以載入到這個類
package org.slf4j.impl;

public final class StaticLoggerBinder implements LoggerFactoryBinder {
// log4j init...
}
複製程式碼
  1. 其他日誌框架也定義了StaticLoggerBinder類,且實現了LoggerFactoryBinder介面。 例如: logback,jcl
// ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class

// 這裡的包名定義為org.slf4j.impl,使得上述第2步的Class Loader可以載入到這個類
package org.slf4j.impl;

public class StaticLoggerBinder implements LoggerFactoryBinder {
// logback init...
}
複製程式碼
// org/slf4j/slf4j-jcl/1.7.25/slf4j-jcl-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class

// 這裡的包名定義為org.slf4j.impl,使得上述第2步的Class Loader可以載入到這個類
package org.slf4j.impl;

public class StaticLoggerBinder implements LoggerFactoryBinder {
// jcl init...
}
複製程式碼

問題答案

  • SLF4J是如何整合不同的日誌框架的

SLF4J通過載入各個底層日誌框架橋接庫的org/slf4j/impl/StaticLoggerBinder.class來載入初始化對應的日誌框架。

  • Class Path中為什麼只能有且僅有一種日誌框架的binding

slf4j-api中的LoggerFactory中,在上述第2步返回staticLoggerBinderPathSet後,會立馬呼叫 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet)方法來檢測是否有多個binding。如果有多個binding,就輸出錯誤資訊"Class path contains multiple SLF4J bindings."

private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
    if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
        Util.report("Class path contains multiple SLF4J bindings.");
        Iterator i$ = binderPathSet.iterator();

        while(i$.hasNext()) {
            URL path = (URL)i$.next();
            Util.report("Found binding in [" + path + "]");
        }

        Util.report("See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.");
    }

}
複製程式碼

最後

程式碼中使用SLF4J來記錄日誌,可以任意切換底層日誌框架而不需要修改程式碼,只需要更改依賴以及日誌配置檔案即可。

相關文章