slf4j初始化綁定源碼分析

通過閱讀源碼研究一下 Slf4j 是如何在運行時綁定具體的log api實現。

源碼追蹤

slf4j-api的源碼

我們來看看slf4j的源代碼,看當這段常見的寫日誌代碼在第一次執行時,slf4j會如何工作

Logger logger = LoggerFactory.getLogger(SomeClass.class);
logger.debug("first log");

打開類 org.slf4j.LoggerFactory的源碼,看到getLogger()方法的代碼如下:

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

繼續看 getILoggerFactory()方法的代碼實現:

static int INITIALIZATION_STATE = UNINITIALIZED;

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        INITIALIZATION_STATE = ONGOING_INITIALIZATION;
        performInitialization();
    }

    switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
    ...
}

這裏涉及到一個名爲INITIALIZATION_STATE 的靜態變量,用來記錄當前初始化的狀態。默認是UNINITIALIZED,第一次調用getILoggerFactory()方法時,檢查到INITIALIZATION_STATE == UNINITIALIZED,就會調用performInitialization()方法來進行初始化。

performInitialization()方法在初始化完成時,會設置INITIALIZATION_STATE爲SUCCESSFUL_INITIALIZATION。這樣後面的switch語句就會調用StaticLoggerBinder.getSingleton().getLoggerFactory()來返回需要的ILoggerFactory。

我們繼續深入看performInitialization()方法的代碼實現:

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

繼續看bind()方法,忽略錯誤處理的代碼:

private final static void bind() {
    try {
      Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
      reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
      // the next line does the binding
      StaticLoggerBinder.getSingleton();
      INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
      reportActualBinding(staticLoggerBinderPathSet);
      emitSubstituteLoggerWarning();
    } ......
}

這裏是關鍵代碼了,findPossibleStaticLoggerBinderPathSet()方法用來查找當前classpath下可能的StaticLoggerBinder的實現,如果有多個的話,則reportMultipleBindingAmbiguity()和reportActualBinding()方法會在綁定前後打印相應的信息。

看findPossibleStaticLoggerBinderPathSet()裏面幹了什麼:

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

private static Set findPossibleStaticLoggerBinderPathSet() {
    // use Set instead of list in order to deal with  bug #138
    // LinkedHashSet appropriate here because it preserves insertion order during iteration
    Set staticLoggerBinderPathSet = new LinkedHashSet();
    try {
      ClassLoader loggerFactoryClassLoader = LoggerFactory.class
              .getClassLoader();
      Enumeration paths;
      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 ioe) {
      Util.report("Error getting resources from path", ioe);
    }
    return staticLoggerBinderPathSet;
}

忽略細節,findPossibleStaticLoggerBinderPathSet()方法其實就是通過classloader的getResources()方法找到所有的名爲”org/slf4j/impl/StaticLoggerBinder.class”的resource。

我們再回來看bind()方法,其實真正綁定的代碼只有一行

import org.slf4j.impl.StaticLoggerBinder;

private final static void bind() {
    ......
    // the next line does the binding
    StaticLoggerBinder.getSingleton();
    ......
}

這裏調用了StaticLoggerBinder.getSingleton()方法。我們看StaticLoggerBinder類的權限定名,恰好和findPossibleStaticLoggerBinderPathSet()方法中查找的一致。

StaticLoggerBinder類是從哪裏來的?我們看代碼的時候,可以發現在slf4j-api的源代碼中,的確有對應的package和類存在。

ImplClassInSlf4jApiSource.jpgImplClassInSlf4jApiSource.jpg

但是打開打包好的slf4j-api.jar,卻發現根本沒有這個implpackage。

ImplClassNotInSlf4jApiJar.jpgImplClassNotInSlf4jApiJar.jpg

而且這個StaticLoggerBinder類的代碼也明確說這個類不應當被打包到slf4j-api.jar:

private StaticLoggerBinder() {
    throw new UnsupportedOperationException(
        "This code should have never made it into slf4j-api.jar");
}

在slf4j-api項目的pom.xml文件中,我們可以找到下面的內容:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
      <execution>
        <phase>process-classes</phase>
        <goals>
         <goal>run</goal>
        </goals>
      </execution>
    </executions>
    <configuration>
      <tasks>
        <echo>Removing slf4j-api's dummy StaticLoggerBinder and StaticMarkerBinder</echo>
        <delete dir="target/classes/org/slf4j/impl"/>
      </tasks>
    </configuration>
</plugin>

這裏通過調用ant在打包爲jar文件前,將package org.slf4j.impl和其下的class都刪除掉了。

實際上這裏的impl package內的代碼,只是用來佔位以保證可以編譯通過(所謂dummy)。需要在運行時再進行綁定。

##具體的log api的源碼

我們再來看,具體的log api實現要如何做才能和slf4j綁定和裝載。

slf4j自帶了一個極度簡化的log實現slf4j-simple,這裏我們可以找到slf4j-api需要的”org/slf4j/impl/StaticLoggerBinder.class”:

StaticLoggerBinderInSlf4jSimple.jpgStaticLoggerBinderInSlf4jSimple.jpg

同樣在slf4j-log4j12, slf4j-jkd14, slf4j-jcl的項目中可以找到類似的”org/slf4j/impl/StaticLoggerBinder.class”,這三個項目分別用於集成java社區最常見的幾個log實現:log4j, jdk logging, apache common logging.

繼續看回StaticLoggerBinder的代碼,以slf4j-simple爲例:

private final ILoggerFactory loggerFactory;

private StaticLoggerBinder() {
  loggerFactory = new SimpleLoggerFactory();
}

public ILoggerFactory getLoggerFactory() {
  return loggerFactory;
}

這裏的getLoggerFactory()方法會返回slf4j-simple實現的SimpleLoggerFactory。

public class SimpleLoggerFactory implements ILoggerFactory {

    ConcurrentMap<String, Logger> loggerMap;

    public Logger getLogger(String name) {
        Logger simpleLogger = loggerMap.get(name);
        if (simpleLogger != null) {
            return simpleLogger;
        } else {
            Logger newInstance = new SimpleLogger(name);
            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }
}

SimpleLoggerFactory實現slf4j定義的ILoggerFactory interface,getLogger()方法中負責創建SimpleLogger對象並返回(爲了提高性能做了cache)。

類似的slf4j-log4j12中會返回Log4jLoggerFactory,而Log4jLoggerFactory中通過調用log4j的LogManager來創建log4j的Logger對象並通過Log4jLoggerAdapter類來包裝爲slf4j的Logger(adapter模式)。

log4jLogger = LogManager.getLogger(name);
Logger newInstance = new Log4jLoggerAdapter(log4jLogger);

代碼和類分析

初始化流程中涉及到的類:

  1. org.slf4j.Logger
  2. org.slf4j.LoggerFactory
  3. org.slf4j.ILoggerFactory
  4. org.slf4j.impl.StaticLoggerBinder
  5. org.slf4j.ILoggerFactory
  6. 具體log api的Logger實現類

Logger和LoggerFactory是slf4j定義好的,業務代碼通過LoggerFactory來創建Logger對象,並調用這個logger對象來寫日誌。業務代碼在此時是無需知道(也無法知道)具體底層是哪個log api實現,從而擺脫對具體log api的依賴。

LoggerFactory通過裝載StaticLoggerBinder類來綁定具體的log api實現,得到該log api實現ILoggerFactory接口的類示例。

這個ILoggerFactory接口的類示例調用底層log api的實現來獲取需要logger。

這裏有個細節,如果該Logger類已經實現了org.slf4j.Logger這個interface,就直接返回。比如綁定slf4j-simple時:

slf4j-bind-simple.pngslf4j-bind-simple.png

如果沒有,比如Log4j的Logger,肯定不會實現org.slf4j.Logger這個interface,這時就需要包裝爲slf4j的Logger。在slf4j-log4jl2中,有一個Log4jLoggerAdapter類,實現了org.slf4j.Logger, 然後將方法調用轉發給log4j的Logger:

slf4j-bind-log4j.pngslf4j-bind-log4j.png

總結

通過翻看Slf4j的代碼,我們可以清楚的看到slf4j在運行時綁定具體的log api實現的方式。其實非常簡單,關鍵之處就在於 org.slf4j.impl.StaticLoggerBinder 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章