SLF4J的實(shí)現(xiàn)原理

系列

SLF4J介紹

?slf4j,simple logging facade for java的縮寫,翻譯為java的簡單日志外觀。slf4j是一個(gè)開源項(xiàng)目,它提供我們一個(gè)一致的API來使用不同的日志框架(如java.util.logging,logback,log4j等)。slf4j使用戶可以在運(yùn)行時(shí)嵌入他們想使用的日志框架。從名字中可以看出,它其實(shí)使用的是facade設(shè)計(jì)模式來實(shí)現(xiàn)的。

?使用slf4j,只有一個(gè)強(qiáng)制性的依賴,就是slf4j-api-x.x.x.jar,我們在編寫代碼的時(shí)候,只會使用這個(gè)jar包里的API,應(yīng)用程序在運(yùn)行時(shí)去類路徑下查找綁定的具體日志框架,并使用該綁定的日志框架進(jìn)行實(shí)際的日志操作,如果在應(yīng)用程序的類路徑下面沒有找到合適的綁定的話,slf4j默認(rèn)使用一個(gè)沒有任何操作的實(shí)現(xiàn)。

?slf4j bindings(以slf4j-api的1.7.7版本為例)slf4j為不同的日志框架提供了不同的綁定,在slf4j提供的分發(fā)包里包含了一些綁定。

  • slf4j-log4j12-x.x.x.jar是使用org.apache.log4j.Logger提供的驅(qū)動(dòng)。
  • slf4j-jdk14-x.x.x.jar是使用java.util.logging提供的驅(qū)動(dòng)。
  • slf4j-simple-x.x.x.jar直接綁定System.err。
  • slf4j-jcl-x.x.x.jar是使用commons-logging提供的驅(qū)動(dòng)。
  • logback-classic-x.x.x.jar是使用logback提供的驅(qū)動(dòng)。


SLF4J基本用法

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.30</version>
    </dependency>
</dependencies>
  • 使用slf4j需要依賴slf4j-api和其他日志框架(這里以slf4j-log4j12-x.x.x.jar)框架為例。
public class Demo {
    public static void main(String[] args) {
        LoggerFactory.getLogger(Demo.class).error("+++++++");
    }
}
  • 使用slf4j通過LoggerFactory.getLogger的api獲取Logger對象進(jìn)而打印日志即可。


SLF4J核心邏輯介紹

SLF4J的原理圖

  • 應(yīng)用application通過slf4j橋接器橋接不同的日志框架,通過slf4j api進(jìn)行日志打印的操作。
  • 從本質(zhì)上來講slf4j主要是通過不同的橋接器對接了不同的日志框架,具體的日志打印仍然由日志框架去實(shí)現(xiàn)。

SLF4J的工作流程

?SLF4J通過slf4j-api訪問橋接器slf4j-log4j12生成LoggerFactory對象,通過LoggerFactory對象的getLogger返回橋接器的LoggerAdapter對象,通過LoggerAdapter的info方法輸出日志,整體的執(zhí)行過程如下圖。

slf4j時(shí)序圖原理.png
  • SLF4J的工作流程核心包含獲取Logger對象流程和打印日志流程。
  • SLFTJ通過slf4j-api調(diào)用log4j橋接器slf4j-log4j12的StaticLoggerBinder生成LoggerFactory對象。
  • SLFTJ通過slf4j-api的getLogger方法訪問橋接器生產(chǎn)Logger對象。
  • SLFTJ通過橋接器的Logger對象實(shí)現(xiàn)日志的格式化和打印。

SLF4J的流程源碼

public final class LoggerFactory {
    public static ILoggerFactory getILoggerFactory() {

        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    // 執(zhí)行LoggerFactory的初始化過程
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            // 返回獲取的LoggerFactory對象
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        }
        throw new IllegalStateException("Unreachable code");
    }
}
  • getILoggerFactory通過performInitialization完成橋接器LoggerFactory的初始化過程。
  • 通過橋接器的StaticLoggerBinder.getSingleton().getLoggerFactory()返回LoggerFactory對象。

public final class LoggerFactory {

    private final static void performInitialization() {
        // 核心的bind過程
        bind();
    }

    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;

            if (!isAndroid()) {
                // 查詢所有的StaticLoggerBinder的類
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // 通過靜態(tài)方法調(diào)用構(gòu)造LoggerFactory對象
            StaticLoggerBinder.getSingleton();

        } catch (Exception e) {
        } 
    }

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            // 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);
            }
            // 查找所有的StaticLoggerBinder類對象
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
        }
        return staticLoggerBinderPathSet;
    }
  • performInitialization的核心邏輯是初始化slf4j橋接器的LoggerFactory對象。
  • findPossibleStaticLoggerBinderPathSet加載所有橋接器的StaticLoggerBinder的類對象。
  • slf4j的必須配置橋接器,存在多個(gè)橋接器的情況下會選擇其中一個(gè)橋接器進(jìn)行工作。
  • slf4j的核心依賴slf4j-api和slf4j-log412橋接器,兩者必須同時(shí)使用。

SLF4J設(shè)計(jì)模式

public class StaticLoggerBinder implements LoggerFactoryBinder {

  private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

  public static final StaticLoggerBinder getSingleton() {
    return SINGLETON;
  }

  private StaticLoggerBinder() {
    loggerFactory = new Log4jLoggerFactory();
    try {
      @SuppressWarnings("unused")
      Level level = Level.TRACE;
    } catch (NoSuchFieldError nsfe) {
    }
  }

  public ILoggerFactory getLoggerFactory() {
    return loggerFactory;
  }
}
  • StaticLoggerBinder作為適配器的核心類,核心思想是通過單例模式返回面向接口的對象ILoggerFactory。
  • 其中個(gè)人認(rèn)為可以參考的點(diǎn)在于單例的設(shè)計(jì)模式和面向接口的編程模式,SLF4J-API定義ILoggerFactory接口,各適配類具體實(shí)現(xiàn)。
public interface ILoggerFactory {
  public Logger getLogger(String name);
}


public class Log4jLoggerFactory implements ILoggerFactory {

  ConcurrentMap<String, Logger> loggerMap;

  public Log4jLoggerFactory() {
    loggerMap = new ConcurrentHashMap<String, Logger>();
  }

  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;
    }
  }
}
  • Log4jLoggerFactory實(shí)現(xiàn)接口ILoggerFactory并提供getLogger來返回Logger對象。
  • getLogger內(nèi)部通過適配不同日志框架的獲取Logger的方法,以slf4j-log4j12為例適配log4j,內(nèi)部封裝LogManager.getLogger(name)來獲取Logger對象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容