common-logging源碼解析

OK,如今咱們來研究下common-logging的源碼。這篇博客有參照上善若水的博客,感謝他的無私分享。java

先來隨便扯點吧,貌似全部這些流行的Logging框架都和Log4J多少有點關係(不太肯定Commons Logging有多大關係,不過至少也都是Apache下的項目吧)。JDK Logging聽說當初是想用Log4J的,可是當時兩家好像談判談崩了,而後JDK本身實現了一個,貌似結構和Log4J差很少,只是實現的比較爛,基本上也只能在作測試的時候用,而SLF4J和LogBack都是出自Log4J的創始人Ceki Gülcü之手。這傢伙也算是閒的蛋疼,光整Logging這些框架貌似就花了很多時間吧。

web

  • common-logging的包結構

原本我要下載這個包的源碼而後在本地跑一下的,結果一看jar包就這麼幾個類,因此呢?這裏也就不直接跑源碼和調試源碼了。common-logging的包結構以下:apache


OK,言歸正傳,在Logging系統中,目前框架都是基於相同的設計,即從一個LogFactory中取得一個命名的Log(Logger)實例,而後使用這個Log(Logger)實例打印debug、info、warn、error等不一樣級別的日誌。做爲兩個門面日誌系統,Commons Logging和SLF4J也一樣採用這樣的設計。所謂門面日誌系統,是指它們自己並不實現具體的日誌打印邏輯,它們只是做爲一個代理系統,接收應用程序的日誌打印請求,而後根據當前環境和配置,選取一個具體的日誌實現系統,將真正的打印邏輯交給具體的日誌實現系統,從而實現應用程序日誌系統的「可插拔」,便可以經過配置或更換jar包來方便的更換底層日誌實現系統,而不須要改變任何代碼。我的感受SLF4J的實現更加靈活,而且它還提供了Maker和MDC的接口。關於SLF4J的整理我在後面會作具體介紹。設計模式

  • LogFactory獲取相對應的Log實現類源碼

Commons Logging的設計比較簡單,它定義了一個Log接口,全部它支持的日誌系統都有相應的Log實現類,如Log4JLogger、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog、NoOpLog、AvalonLogger、LogKitLogger等類,在LogFactory中定義了必定的規則,從而根據當前的環境和配置取得特定的Log子類實例。咱們在實際編碼中經過下面代碼來獲取一個log實例:

public static Log LOG = LogFactory.getLog(CommonsLoggingTest.class);

這裏貼出LogFactory獲取相對應的Log實現類的核心代碼:數組

LogFactory抽象類:
緩存

 public static Log getLog(Class clazz) throws LogConfigurationException {
        return getFactory().getInstance(clazz);
    }

LogFactoryImpl實現類:服務器

public Log getInstance(Class clazz) throws LogConfigurationException {
        return getInstance(clazz.getName());
    }
public Log getInstance(String name) throws LogConfigurationException {
        Log instance = (Log) instances.get(name);
        if (instance == null) {
            instance = newInstance(name);
            instances.put(name, instance);
        }
        return instance;
    }
protected Log newInstance(String name) throws LogConfigurationException {
        Log instance;
        try {
            if (logConstructor == null) {
                instance = discoverLogImplementation(name);
            }
            else {
                Object params[] = { name };
                instance = (Log) logConstructor.newInstance(params);
            }

            if (logMethod != null) {
                Object params[] = { this };
                logMethod.invoke(instance, params);
            }

            return instance;

        }
關於上面代碼解釋:

若是在獲取具體的實例的時候,common-logging的logConstructor屬性不爲空,則直接拋下反射初始化該實例就OK,若是該屬性爲空,那麼就要按照順序去找對應的日誌實例。核心代碼以下:app

 private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {

//1,初始化配置
initConfiguration();
//2,尋找用戶自定義的日誌實例
Log result = null;
String specifiedLogClassName = findUserSpecifiedLogClassName();
//3,框架開始工做,按照順序初始化log實例
if (specifiedLogClassName != null) {
    // 初始化log實例
    result = createLogFromClass(specifiedLogClassName,logCategory,true);
    if (result == null) {
        StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");
        messageBuffer.append(specifiedLogClassName);
        messageBuffer.append("' cannot be found or is not useable.");

        // 順序鏈接報錯字符串,拋出一個異常
        informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
        informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
        informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
        informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
        throw new LogConfigurationException(messageBuffer.toString());
    }

    return result;
 }


  • LogFactory獲取相對應的Log實現類邏輯

Commons Logging中默認實現的LogFactory(LogFactoryImpl類)查找具體Log實現類的邏輯以下:


1.    查找在commons-logging.properties文件中是否認存在以org.apache.commons.logging.Log或org.apache.commons.logging.log(舊版本,不建議使用)爲key定義的Log實現類,若是是,則使用該類。


2.    不然,查找在系統屬性中(-D方式啓動參數)是否存在以org.apache.commons.logging.Log或org.apache.commons.logging.log(舊版本,不建議使用)爲key定義的Log實現類,若是是,則使用該類。


3.    不然,若是在classpath中存在Log4J的jar包,則使用Log4JLogger類。


4.    不然,若是當前使用的JDK版本或等於1.4,則使用Jdk14Logger類。


5.    不然,若是存在Lumberjack版本的Logging系統,則使用Jdk13LumberjackLogger類。


6.    不然,若是能夠正常初始化Commons Logging自身實現的SimpleLog實例,則使用該類


7.    最後,以上步驟都失敗,則拋出LogConfigurationException。

  • Commons Logging還支持用戶自定義的LogFactory實現類


其實,Commons Logging還支持用戶自定義的LogFactory實現類。對LogFactory類的查找邏輯爲:


1.    查看系統屬性中是否存在以org.apache.commons.logging.LogFactory爲key的LogFactory實現類,如有,則使用該類實例化一個LogFactory實例。


2.    不然,嘗試使用service provider的方式查找LogFactory實現類,即查看classpath或jar包中是否存在META-INF/services/org.apache.commons.logging.LogFactory文件,若是存在,則使用該文件內定義的LogFactory類實例化一個LogFactory實例。


3.    不然,查找commons-logging.properties文件是否存在,而且其中存在以org.apache.commons.logging.LogFactory爲key的LogFactory實現類,如有,則使用該類實例化一個LogFactory實例。


4.    不然,使用默認的LogFactoryImpl實現類實例化一個LogFactory實例。


Commons Logging的類設計圖以下:




在使用Commons Logging時,常常在服務器部署中會遇到ClassLoader的問題,這也是常常被不少人所詬病的地方,特別是在和Log4J一塊兒使用的時候。常見的如,因爲Common Logging使用很是普遍,於是不少Web容器(WebSphere)在內也會使用它做爲日誌處理系統而將其jar包引入到容器自己中,此時LogFactory是使用Web容器自己的ClassLoader裝載的,即便Log4J中使用了ContextClassLoader來查找配置文件,此時的Thread依然在容器中,於是它使用的ClassLoader仍是容器自己的ClassLoader實例,此時須要把Log4J的配置文件放到共享目錄下,該配置文件才能被正常識別。在WebSphere還能夠經過設置類的加載順序爲PARENT_LAST的方法來解決。而在Jboss中則只能將本身的配置加到其conf下的Log4J配置文件中,由於Jboss默認導入Log4J包。


  • Commons Logging的具體實現:



在使用Commons Logging時,通常是經過LogFactory獲取Log實例,而後調用Log接口中相應的方法。於是Commons Logging的實現能夠分紅如下幾個步驟:


  • LogFactory類初始化

a.    緩存加載LogFactory的ClassLoader(thisClassLoader字段),出於性能考慮。由於getClassLoader()方法可能會使用AccessController(雖然目前並無使用),於是緩存起來以提高性能。

b.    初始化診斷流。讀取系統屬性org.apache.commons.logging.diagnostics.dest,若該屬性的值爲STDOUT、STDERR、文件名。則初始化診斷流字段(diagnosticStream),並初始化診斷消息的前綴(diagnosticPrefix),其格式爲:」[LogFactory from <ClassLoaderName@HashCode>] 「, 該前綴用於處理在同一個應用程序中可能會有多個ClassLoader加載LogFactory實例的問題。

c.    若是配置了診斷流,則打印當前環境信息:java.ext.dir、java.class.path、ClassLoader以及ClassLoader層級關係信息。

d.    初始化factories實例(Hashtable),用於緩存LogFactory(context-classloader –-> LogFactory instance)。若是系統屬性org.apache.commons.logging.LogFactory.HashtableImpl存在,則使用該屬性定義的Class做爲factories Hashtable的實現類,不然,使用Common Logging實現的WeakHashtable。若初始化沒有成功,則使用Hashtable類自己。使用WeakHashtable是爲了處理在webapp中,當webapp被卸載是引發的內存泄露問題,即當webapp被卸載時,其ClassLoader的引用還存在,該ClassLoader不會被回收而引發內存泄露。於是當不支持WeakHashtable時,須要卸載webapp時,調用LogFactory.relase()方法。

e.    最後,若是須要打印診斷信息,則打印「BOOTSTRAP COMPLETED」信息

  • 查找LogFactory類實現,並實例化

當調用LogFactory.getLog()方法時,它首先會建立LogFactory實例(getFactory()),而後建立相應的Log實例。getFactory()方法不支持線程同步,於是多個線程可能會建立多個相同的LogFactory實例,因爲建立多個LogFactory實例對系統並無影響,於是能夠不用實現同步機制。

a.    獲取context-classloader實例。

b.    從factories Hashtable(緩存)中獲取LogFactory實例。

c.    讀取commons-logging.properties配置文件(若是存在的話,若是存在多個,則能夠定義priority屬性值,取全部commons-logging.properties文件中priority數值最大的文件),若是設置use_tccl屬性爲false,則在類的加載過程當中使用初始化cache的thisClassLoader字段,而不用context ClassLoader。

d.    查找系統屬性中是否存在org.apache.commons.logging.LogFactory值,如有,則使用該值做爲LogFactory的實現類,並實例化該LogFactory實例。

e.    使用service provider方法查找LogFactory的實現類,並實例化。對應Service ID是:META-INF/services/org.apache.commons.logging.LogFactory

f.     查找commons-logging.properties文件中是否認義了LogFactory的實現類:org.apache.commons.logging.LogFactory,是則用該類實例化一個出LogFactory

g.    不然,使用默認的LogFactory實現:LogFactoryImpl類。

h.    緩存新建立的LogFactory實例,並將commons-logging.properties配置文件中全部的鍵值對加到LogFactory的屬性集合中。

  • 經過LogFactory實例查找Log實例(LogFactoryImpl實現)

使用LogFactory實例調用getInstance()方法取得Log實例。

a.    若是緩存(instances字段,Hashtable)存在,則使用緩存中的值。

b.    查找用戶自定義的Log實例,即從先從commons-logging.properties配置文件中配置的org.apache.commons.logging.Log(org.apache.commons.logging.log,舊版本)類,若不存在,查找系統屬性中配置的org.apache.commons.logging.Log(org.apache.commons.logging.log,舊版本)類。若是找到,實例化Log實例

c.    遍歷classesToDiscover數組,嘗試建立該數組中定義的Log實例,並緩存Log類的Constructor實例,在下次建立Log實例是就不須要從新計算。在建立Log實例時,若是use_tccl屬性設爲false,則使用當前ClassLoader(加載當前LogFactory類的ClassLoader),不然儘可能使用Context ClassLoader,通常來講Context ClassLoader和當前ClassLoader相同或者是當前ClassLoader的下層ClassLoader,然而在不少自定義ClassLoader系統中並無設置正確的Context ClassLoader致使當前ClassLoader成了Context ClassLoader的下層,LogFactoryImpl默認處理這種狀況,即便用當前ClassLoader。用戶能夠經過設置org.apache.commons.logging.Log.allowFlawedContext配置做爲這個特性的開關。

d.    若是Log類定義setLogFactory()方法,則調用該方法,將當前LogFactory實例傳入。

e.    將新建立的Log實例存入緩存中。

  • 調用Log實例中相應的方法

Log實例中其實就是各個插拔日誌的一個門面,關於門面能夠去看我設計模式相關的文章。這裏就以 Log4JLogger爲例,解釋下該門面log。

下面先貼Log4JLogger核心源碼:框架

public class Log4JLogger implements Log, Serializable {

    /** Serializable version identifier. */
    private static final long serialVersionUID = 5160705895411730424L;


    /** The fully qualified name of the Log4JLogger class. */
    private static final String FQCN = Log4JLogger.class.getName();

    /** Log to this logger */
    private transient volatile Logger logger = null;

    /** Logger name */
    private final String name;

    private static final Priority traceLevel;


    static {
        if (!Priority.class.isAssignableFrom(Level.class)) {
            // nope, this is log4j 1.3, so force an ExceptionInInitializerError
            throw new InstantiationError("Log4J 1.2 not available");
        }

        Priority _traceLevel;
        try {
            _traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null);
        } catch(Exception ex) {
            // ok, trace not available
            _traceLevel = Level.DEBUG;
        }
        traceLevel = _traceLevel;
    }

    // ------------------------------------------------------------ Constructor

    public Log4JLogger() {
        name = null;
    }

    /**
     * Base constructor.
     */
    public Log4JLogger(String name) {
        this.name = name;
        this.logger = getLogger();
    }

    /**
     * For use with a log4j factory.
     */
    public Log4JLogger(Logger logger) {
        if (logger == null) {
            throw new IllegalArgumentException(
                "Warning - null logger in constructor; possible log4j misconfiguration.");
        }
        this.name = logger.getName();
        this.logger = logger;
    }

    /**
     * Logs a message with <code>org.apache.log4j.Priority.DEBUG</code>.
     *
     * @param message to log
     * @see org.apache.commons.logging.Log#debug(Object)
     */
    public void debug(Object message) {
        getLogger().log(FQCN, Level.DEBUG, message, null);
    }

    /**
     * Logs a message with <code>org.apache.log4j.Priority.DEBUG</code>.
     *
     * @param message to log
     * @param t log this cause
     * @see org.apache.commons.logging.Log#debug(Object, Throwable)
     */
    public void debug(Object message, Throwable t) {
        getLogger().log(FQCN, Level.DEBUG, message, t);
    }

    /**
     * Return the native Logger instance we are using.
     */
    public Logger getLogger() {
        Logger result = logger;
        if (result == null) {
            synchronized(this) {
                result = logger;
                if (result == null) {
                    logger = result = Logger.getLogger(name);
                }
            }
        }
        return result;
    }

    /**
     * Check whether the Log4j Logger used is enabled for <code>DEBUG</code> priority.
     */
    public boolean isDebugEnabled() {
        return getLogger().isDebugEnabled();
    }

    
}

關於上面源碼的解釋:

1,首先封裝log4j的logger類做爲屬性,而後在封裝一個name屬性,該屬性用來初始化logger時候的構造器參數,在初始化log4jLogger類的時候該name傳入,而後用該name來初始化logger實例屬性。webapp

2,對外提供一套和log4j一致的API,而後方法中調用logger屬性相關API方法就OK了。這裏沒有直接用屬性.方法(),而是用了getLogger(),這樣子能夠防止logger屬性是null的狀況,代碼比較嚴謹。這點值得咱們學習。

相關文章
相關標籤/搜索