slf4J源碼初探

1、日誌實現工具和日誌接口簡介html

java日誌工具主要包含兩個方面:日誌接口工具和日誌實現工具java

  • 日誌實現工具
  1. Apache 開發了功能強大的log4j,並申請歸入jdkandroid

  2. sun不接受apache 的申請,而是在jdk1.4中本身開發了Java.util.logging
  3. logback出自log4j的做者之手,用於替代log4j,Logback的內核重寫了log4j,在一些關鍵執行路徑上性能提高10倍以上,並且logback不只性能提高了,初始化內存加載也更小了。 logback很是天然實現了slf4j, logback-classic實現了slf4j,做爲slf4j的實現時無需適配器。
  • 日誌接口
  1. slf4j 簡單日誌門面(Simple Logging Facade for Java)。 slf4j一樣是日誌接口,使用靜態綁定的方式支持log4jjdklogback等做爲日誌實現,也支持靜態綁定common-logging做爲中間層,而後經過common-logging的動態加載機制選擇日誌實現。使用SLF4J時,若是須要使用某一種日誌實現,那麼必須選擇正確的適配器,好比:slf4j-log4j12.jar, slf4j- jdk14.jar等
  2. JCL Java通用日誌接口 Apche common-logging。  commons-logging支持log4jjdk Java.util.logging做爲其日誌實現,使用時經過動態加載的方式尋找。 commons-logging啓動後在 classpath 若是能找到log4j,則默認使用log4j 做爲日誌實現,若是沒有則使用JDK14Logger 實現,若是JDK版本低於1.4,則使用其內部提供的 SimpleLog 實現。JCL的缺點:Apache Common-Logging使用了ClassLoader尋找和載入底層的日誌庫。而OSGI中,不一樣的插件使用本身的ClassLoader。一個線程的ClassLoader在執行不一樣的插件時,其執行能力是不一樣的。OSGI的這種機制保證了插件互相獨立,然而確使Apache Common-Logging沒法工做。
  3. JCL 只提供 log 接口,具體的實現則在運行時動態尋找。slf4j一樣也是日誌接口,使用靜態綁定的方式支持log4jjdklogback等做爲日誌實現,也支持靜態綁定common-logging做爲中間層,而後經過common-logging的動態加載機制選擇日誌實現。

2、日誌接口slf4j源碼解析apache

一、引言api

在閱讀本小節前,最好先了解下log4j,logback,jdk-log,slf4j的知識,編寫一個可以打印日誌的demo就能夠了。app

日誌接口不是具體的日誌解決方案,它只服務於各類各樣的日誌系統 。而是一個抽象層( abstraction layer),它容許你在後臺使用任意一個日誌類庫。若是是在編寫供內外部均可以使用的API或者通用類庫,那麼你真不會但願使用你類庫的客戶端必須使用你選擇的日誌類庫。maven

log4j,logback,jdk-log都爲slfj提供了實現,在引入這個三個工具的slf4j的後工具

<dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.7</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.16</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.5.6</version>
    </dependency>

這三個jar中都有StaticLoggerBindder這個類。(當依賴的jar包版本出現衝突(groupId和artifactId相同,但version不一樣)時,優先級規則爲:在依賴樹中越靠近項目根節點的jar優先級越高,若兩個jar在依賴樹中所處層次相同則在pom.xml中越早出現的jar優先級越高。性能

若是兩個jar裏面的package名和class名都相同,可是groupId或artifactId不一樣,例如log4j-1.2.17與橋接器log4j-over-slf4j-1.7.21就符合這樣的情況,程序加載org.apache.log4j.LogManager類的順序適用於上述優先級規則。)ui

logback有些不同,這點留到咱們後面再講。

至於爲何,咱們一步一步看。咱們在項目中引入slf4j-api,slf4j-xxx,xxx三個jar包以後,下面代碼就能夠正確的執行

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

public class App {
    public static Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        logger.info("Hello {}!", "world");
    }
}

在上述代碼中並無引入log4j,logback,jdk-log相關的類,而當咱們去掉slf4j-xxx這個jar包以後,就會提示如下錯誤:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

咱們能夠猜想,在LoggerFactory或者Loger中引用到了org.slf4j.impl.StaticLoggerBinder。

2.獲取LoggerFactory

2.1 首先看getLogger(class a),這個方法又調用了getLogger(String name)的方法

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

2.2 代碼第一行獲取了一個iLoggerFactory,繼續點進去看

/**
     * Return the {@link ILoggerFactory} instance in use.
     * <p/>
     * <p/>
     * ILoggerFactory instance is bound with this class at compile time.
     * 
     * @return the ILoggerFactory instance in use
     */
    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        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:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

if語句表明,若是沒有初始化的話,就進入一個同步塊執行performInitialization()的方法,若是init成功則返回一個經過StaticLoggerBindder建立的LoggerFactory。(這裏就結合前面提到問題考慮,爲何slf4j-jdk,slf4j-log4j,logback-classic中都包含StaticLoggerBindder類,以及爲引入這三個jar時,slf4j提示的異常)。

2.3 performInitialization()方法裏面調用了bind()方法,而後進行進行版本檢查,這裏咱們重點關注bind()方法。

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
//找到全部的StaticLoggerBinder 
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
/*若是有多個,則提示載入了多個StaticLoggerBinder 實現,可能引入了slf4j-jdk,slf4j-log4j,
logback-classic中的兩個或者三個*/
/*若是三個都引入了,會提示:
SLF4J: Found binding in [jar:file:/E:/maven/repository/
ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven/repository/
org/slf4j/slf4j-log4j12/1.7.16/slf4j-log4j12-1.7.16.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven/repository/
org/slf4j/slf4j-jdk14/1.5.6/slf4j-jdk14-1.5.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
*/
//提示有多個地方有StaticLoggerBinder
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
//獲取StaticLoggerBinder的大力
            StaticLoggerBinder.getSingleton();
//設置狀態,初始化成功
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
//report實際的loggerFactory的類,例如 SLF4J: Actual binding is of type 
//[ch.qos.logback.classic.util.ContextSelectorStaticBinder]
            reportActualBinding(staticLoggerBinderPathSet);
//這句應該是處理在初始化過程當中阻塞的輸入日誌事件
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (.....)
           {...  }
    }

代碼很是長,不過咱們暫時只關心try 塊的代碼,先理清最主要的脈絡。staticLoggerBinderPathSet用來存儲 StaticLoggerBindder的路徑集合。
若是有多個路徑就提示異常。

2.4 而後看找StaticLoggerBindder路徑的代碼,仍是比較好理解的

// We need to use the name of the StaticLoggerBinder class, but we can't reference the class itself.
	private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

	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 iteration
		Set<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);
			}
//將path加入集合中
			while (paths.hasMoreElements())
			{
				URL path = paths.nextElement();
				staticLoggerBinderPathSet.add(path);
			}
		}
		catch (IOException ioe)
		{
			Util.report("Error getting resources from path", ioe);
		}
		return staticLoggerBinderPathSet;
	}

STATIC_LOGGER_BINDER_PATH是類的路徑。首先獲取到LoggerFactory的類加載器。若是找不到,就調用ClassLoader的類方法,查找全部的資源;若是找到了LoggerFactory的類加載器,就根據類加載器找對應的路徑。而後將找到的路徑存入staticLoggerBinderPathSet集合中返回。

2.5 回到2.2小節的代碼,init成功以後直接返回執行下列代碼

 return StaticLoggerBinder.getSingleton().getLoggerFactory();

slf4j中是沒有StaticLogerBinder,得去slf4j-jdk,slf4j-log4j,logback-classic中看

咱們選擇log4j,代碼比較簡單,簡單的單例模式。

public class StaticLoggerBinder implements LoggerFactoryBinder {


    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

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

    public static String REQUESTED_API_VERSION = "1.6.99"; // !final

    private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();

    private final ILoggerFactory loggerFactory;

    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;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

2.6 小節

slf4j-jdk,slf4j-log4j都有slf4j的slf4j logger接口的實現分別是 JDK14LoggerAdapter和Log4jLoggerAdapter,而logback自己就實現了slf4j,不須要額外的適配器。

後面該寫日誌橋接方面的知識

相關文章
相關標籤/搜索