在使用Commons Logging時,經常在服務器部署中會遇到ClassLoader的問題,這也是經常被很多人所詬病的地方,特別是在和Log4J一起使用的時候。常見的如,由于Common Logging使用非常廣泛,因而很多Web容器(WebSphere)在內也會使用它作為日志處理系統而將其jar包引入到容器本身中,此時LogFactory是使用Web容器本身的ClassLoader裝載的,即使Log4J中使用了ContextClassLoader來查找配置文件,此時的Thread依然在容器中,因而它使用的ClassLoader還是容器本身的ClassLoader實例,此時需要把Log4J的配置文件放到共享目錄下,該配置文件才能被正常識別(以我的理解,容器在啟動的時候,它根本無法獲得Web應用程序中的jar包,所以也需要將Log4J的jar包放到共享目錄中才可以,不過我木有用過WebSphere,也沒法測試,所以只能猜測~)。在WebSphere還可以通過設置類的加載順序為PARENT_LAST的方法來解決。而在Jboss中則只能將自己的配置加到其conf下的Log4J配置文件中,因為Jboss默認導入Log4J包。具體可以參考我轉載的一篇文章,Log4j/common log和各種服務器集成的問題(木有經驗,只能用別人的文章了。。。還很可惜的木有機會測試。。。):http://www.tkk7.com/DLevin/archive/2012/11/02/390639.html,另外還找到一篇更加詳細的描述Commons Logging中存在的ClassLoader問題的文章:http://articles.qos.ch/classloader.html
Commons Logging的具體實現:
在使用Commons Logging時,一般是通過LogFactory獲取Log實例,然后調用Log接口中相應的方法。因而Commons Logging的實現可以分成以下幾個步驟:
1. 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”信息
2. 查找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的屬性集合中。
3. 通過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實例存入緩存中。
4. 調用Log實例中相應的方法
Log接口比較簡單,并且Log4J、JDK相應的實現類也都直接代理給各自框架,因而實現比較簡單,不在詳述。關于SimpleLog,可以類似的參考http://www.tkk7.com/DLevin/archive/2012/06/12/380647.html。 不過還是有必要對Jdk14Logger的實現吐槽一下,每次調用log方法時都會創建新的Throwable實例,然后去計算ClassName和Method,這會引起嚴重的性能問題,因為創建一個Throwable實例,意味著需要停止當前的運行,dump出一個調用??煺铡K鼮槭裁床幌?/span>Jdk13LumberjackLogger的實現一樣,把這兩個字段緩存起來呢??