java-web環境整合各種主流日誌框架(jcl,jul,slf4j,log4j,logback)總結
最近一段時間,學習了java日誌管理方面的內容,用過很久,但是沒有系統的學習相關。 趁這個機會,將各種日誌相關的內容,以及框架,和整合使用方面的內容拉通學習一下。
我將從以下的方面進行筆記的整理:
1.log4j1與log4j2在web整合方面的區別;
2.日誌框架標準與實現的區別,及舉例;面向框架的spring-jcl以及slf4j標準,在web方面的使用記錄;
3.slf4j與其它各種日誌框架的整合使用;
4.slf4j日誌框架自動發現的實現原理,及其原始碼解讀;
5.各種日誌框架(不包括log4j2)自動載入配置檔案的原理;
6.如何進行日誌框架的無縫遷移;
log4j1與log4j2在web整合方面的區別
我們應當知道,javaweb專案與一般的java專案是有區別的。 這些區別可以體現在: 啟動的邏輯不同;環境上下文不同;使用者互動方式不同等等。 因為web專案很多很多的框架可以使用,針對不同的目標型別,不同的邏輯構成。有些是不同的邏輯元件,有些是相同的業務邏輯元件不同的實現。 所以可複用元件,或者說標準尤為重要。 日誌元件就是這樣的一種存在。
web環境搭建就沒有什麼必要介紹了。 以前記錄過一篇:地址在這裡
看一下log4j1的使用:
依賴:
使用:
配置:
這樣一個log4j的日誌就可以使用了。 不需要其它任何額外的配置。
在看一看log4j2的使用(基於web專案):
依賴:
使用:
配置:
註冊web上下文:
這樣,log4j2就可以在專案中使用了。
實際上,這裡的log4j2採用的是spring-jcl標準。 於是這就引出第二個問題了。
2.日誌框架標準與實現的區別,及舉例;面向框架的spring-jcl以及slf4j標準,在web方面的使用記錄:
先說第一個問題:標準與實現
至此,我們實際上已經接觸了很多對這樣的邏輯關係的元件:
比如:
jdbc標準,由j2ee標準提出,對應的實現有:mysql的實現,sql-server的實現,oracle的實現。 其中,mysql的實現: mysql-connector.jar,由mysql官方提供。
servlet標準,由j2ee標準提出,對應的實現有: tomcat伺服器容器,jetty容器,websocket,websphere等。 其中典型的tomcat由apache基金會提供並開源。
orm標準,由j2ee標準提出,對應的是是現有: mybatis,hibernate。
datasource標準,由jdk定義,對應的實現有: dbcp2實現,spring的相關實現,druid的實現。 其中druid由阿里巴巴提供。
類似於這樣的標準與實現還有很多很多。。
要說它們的本質,那就是一堆介面,與一堆實現了這些介面的類組成。 當然在這個途中進行了大量的設計模式的使用,效能的考量,相應的擴充套件等。
日誌框架的標準與實現也是這樣。
第二個問題:spring-jcl:
spring-jcl的原始碼較少,因此我們不妨將它的原始碼類結構貼上:
可見,它實際上整合的是apache的commons-loging。 只不過,進行了一些適配。 其中的關鍵是:LogFactory抽象類,以及LOG介面標準。 它的核心原始碼片段:
以上給是該類初始化的靜態塊方法,它會根據上下文就進行適配,需注意它們實際上是有優先順序關係的。 原理很簡單,就是用類載入器去載入相應日誌框架的spi,若找到,則判定為當前系統採用該日誌框架。 否則依次進行迭代,直至採用jul。 因為它位於jdk中,無論如何都會有該日誌框架的。
通過原始碼可以看到,它提供了三個大型別的日誌框架實現,分別是: log4j2日誌框架實現(因為該spi類只存在Log4j2中);slf4j日誌框架標準;jul(java util logging)日誌實現。 它們具有一定的優先順序關係。
至於為什麼是Log4j2,可以給出原始碼驗證:
ok,這是spring-jcl作為日誌標準為基礎的邏輯思路。 我上文使用的slf4j並沒有說它是日誌實現,而是標準。 為什麼這麼說呢? 這就是接下來的一個問題:
slf4j標準:
先看一看slf4j的專案結構:
這也很好的印證了spring-jcl的動態發現,選擇日誌實現框架。
slf4j的標準是由這裡的Logger介面提供的。 至於具體的實現,將在後面的問題中進行討論。
最後,來看一看在web專案的中的使用:
涉及到使用,那麼必須的是標準的實現。 由於spring-jcl預設優先順序的關係,log4j2我們必須先註釋掉,然後才能使用slf4j。 但是我們知道,這是一個標準,如何使用呢?
通過蒐集相關的資料,我們瞭解到,slf4j對於主流的日誌框架實現都提供了適配,或者基於該標準進行開發。
如:slf4j-simple,它的依賴:
它是一個實現類,實現了slf4j的logger介面。
slf4j-jdk4,它的依賴是:
他提供的是基於slf4j的logger介面,針對java.util,logging實現的適配類。 相應的實現由Jdk提供實現,無需依賴其它的第三方。
slf4j-jcl,它的依賴:
它提供的是基於slf4j的logger介面的,針對commons-logging實現的適配類,它需要依賴jcl的實現:
slf4j-log4j12,它的依賴:
它提供的是基於slf4j的logger介面的,針對log4j實現的適配類,它需要依賴log4j的實現,這裡使用的是log4j1:
logbak,它的依賴:
它提供的是基於slf4j的Logger介面的實現類。
以上所說的適配類,以及實現類可以通過檢視繼承關係得到驗證:
注意,這裡有一個有趣的現象,spring-jcl 作為日誌框架為其它日誌實現提供支援,包括對slf4j提供支援。 spring-jcl是基於commons-logging實現的; 而作為slf4j日誌標準來說,它同時也提供了對jcl的整合支援。 這就感覺有那麼一絲絲怪異,形成了一個類似環形的結構。。 不過,這只是感覺上來說,spring-jcl中的那個日誌工廠與commons-logging的日誌工廠應該並不相同,只不過他們提供了相同的包結構。如果是這樣的話, 在一定程度上可能會存在潛在的bug。 具體怎樣等以後有興趣了在看看看吧。
如何使用:
針對這些slf4j標準的實現,它的原理與spring-jcl優點相似,就是在執行時動態的尋找實現。 因此,這裡舉一個例子,其它的類推即可,至於適配的那種,則需要根據本身框架實現來進行配置使用即可。 比如,就拿Simple來說:
第一步,當然是引入相關依賴;
第二步,建立配置檔案:
如:
第三步,在相應的位置使用日誌框架,它會自動掃描,動態尋找依賴,如spring-jcl:
執行,即可。 其它的日誌使用類似!! 包括logback。 這樣的話,簡單的問題背後難免引起我們的思考。 因此這就是接下來思考的問題了。
slf4j日誌框架自動發現的實現原理,及其原始碼解讀:
這個問題實際上可以與spring-jcl類比著看。 先回顧一下spring-jcl是如何實現自動選擇實現的: 它實際上是通過類載入器去載入特定的類,如果載入成功,則認為使用該框架作為實現,否則就一路迭代,選擇其它的實現。 直到最終會確定一個一個實現類。 這個思路有點類似於責任鏈傳遞模式。
那麼slf4j是如何實現的呢?
我主要從兩個方面入手分析了這個問題: 第一,如何確定有哪些實現模組在當前專案中(如何發現); 第二,如何載入實現專案的啟動類(如何實現)。
第一個問題: 我是通過檢視資料,與檢視原始碼進行分析的,過程如下:
這是位於slf4j的日誌工廠的其中一個靜態方法,通過原始碼邏輯,可以分析出,它實際上是完成了獲得實現模組的功能。 這個操作通過類載入器實現:
該類載入器是繼承於URLClassLoader。 它的名稱為: 平行類載入器。 當然前面有一些關於類載入機制的雙親委派機制。 這是jvm部分的基礎知識,也是java的核心知識。 很早以前看過了。(大概昨年春節的時候看過原始碼,也從此開啟奮鬥之旅。)。 需要注意,在web容器中的類載入器機制中,並沒有繼續採用雙親委派機制了。 通過執行時除錯,我們可以分析出,獲得資源的這個實際實現是由tomcat容器的一個類載入器實現的:
(為了實驗的目的,我特意放了兩個依賴進來。)
由於在idea中我並沒有採用內建容器,所以具體的實現細節原始碼沒有查看了。 至此,第一個問題就解決了。
第二個問題:
記得我們之前看過基於Slf4j的實現類,其中除了一些適配類,實現類外,有一個類沒有去描述過它,因為它既不是抽象類,也不是其他類的介面卡類。 並且這個類位於slf4j-api的專案中。
看一看它的原始碼片段:
在slf4j的抽象工廠中有如下的一些關於它的操作:
它的核心方法程式碼片段:
自此,第二個問題也解決的差不多了。 它的實現方法是: 抽象+ 代理
各種日誌框架(不包括log4j2)自動載入配置檔案的原理:
這個問題,各個日誌框架實現類都有各自的實現。 仍然是採用類比的思想,挑一個簡單的來看看,複雜的就不看了。 要說簡單,那自然就是slf4j-simple了。
它的核心業務類: SimpleLogger。
具有如下的一些程式碼片段:
其它自動載入配置檔案的原理估計也差不多。 就不看了。
那麼就剩最後一個問題了:
如何進行日誌框架的無縫遷移:
實際上,這個問題在上面的分析過程中已經解決了。 只要是基於標準實現的,要替換實現,我們要做的僅僅是替換相應的依賴即可,並且這個過程使用者不需要其它額外的配置(當然,對於不同實現之間的必要的適配還是需要做的)。 關於日誌使用的部分,就告一段落。