SLF4J源码分析
介紹
官網(wǎng):http://www.slf4j.org/
github:https://github.com/qos-ch/slf4j
SLF4J(Simple Logging Facade for Java),它為Java的日志系統(tǒng)提供了一套統(tǒng)一的接口(門面),即:作為各種日志框架(java.util.logging,logback,log4j)的抽象。
通過引入SLF4J,可以使項(xiàng)目與logging具體的實(shí)現(xiàn)分離,在提供了一致的接口的同時(shí),提供了靈活選擇logging實(shí)現(xiàn)的能力。(引入SLF4J的庫/應(yīng)用意味著僅添加一個(gè)強(qiáng)制性依賴項(xiàng)slf4j-api.jar)
1、為什么要設(shè)計(jì)出一個(gè)日志接口的抽象層?
我們都知道,日志對于一個(gè)系統(tǒng)來說非常重要。同樣,我們在開發(fā)出了一個(gè)庫時(shí),也需要打印一些調(diào)試或者運(yùn)行日志,而我們系統(tǒng)往往會引入大量的第三方庫。這是,就會遇到一個(gè)問題:假設(shè)我們系統(tǒng)使用的是Log4j日志框架,引入了RMQ庫使用的是Logback框架,這時(shí)系統(tǒng)就出現(xiàn)了兩個(gè)日志框架,維護(hù)起來非常麻煩。
解決這個(gè)問題的方法是引入一個(gè)適配層。例如:
如果我們都是通過SLF4J這種統(tǒng)一的接口,那么RMQ庫在發(fā)布時(shí)就無需帶著具體日志框架的實(shí)現(xiàn),這樣我們系統(tǒng)引入RMQ后,仍然使用的是我們系統(tǒng)中引入的日志實(shí)現(xiàn)了,這樣就方便了維護(hù)。
slf4j只做兩件事情:
- 提供日志接口
- 提供獲取具體日志對象的方法
說明:這種抽象的思想,在軟件開發(fā)中很常見。
2、SLF4J和JCL區(qū)別:
在SLF4J之前,Apache Common Logging(即Jakarta Commons Logging,簡稱JCL)也提供了類似的功能(即:統(tǒng)一的日志接口)。它與SLF4J的區(qū)別在于:
- JCL即提供了統(tǒng)一的接口,也提供了一套默認(rèn)的實(shí)現(xiàn);SLF4J則只提供了接口層
- JCL采用運(yùn)行時(shí)綁定,通過Classloader體系加載相應(yīng)的logging實(shí)現(xiàn);SLF4J采用了靜態(tài)綁定
- SLF4J在接口易用性上更有優(yōu)勢,大大減少了不必要的日志拼接:
- JCL為了避免無效的字符串拼接,一般需要通過if判斷:
- SLF4J則提供了占位符"{}",只在必要的情況下才會進(jìn)行日志字符串處理和拼接:
推薦使用slf4j中占位符原因主要有兩點(diǎn):
- 當(dāng)設(shè)置的日志級別高于某條代碼中的日志級別時(shí),使用占位符可以免掉字符串拼接操作;
- 占位符底層使用的是StringBuilder進(jìn)行的拼接,性能比“+”要好;
注:在SLF4J和JCL中,推薦使用前者。
3、SLF4J使用:
SLF4J的使用非常簡單:
- 引入SLF4J依賴 (slf4j-api.jar)
- 引入一種SLF4J的實(shí)現(xiàn),比如:logback、log4j...
然后:
import org.slf4j.Logger; import org.slf4j.LoggerFactory;public MyClass {Logger logger = LoggerFactory.getLogger(MyClass.class);puhblic void method() {logger.info("hello world...");} }注:從1.6.0開始,如果在類路徑上未找到綁定,則SLF4J將默認(rèn)為無操作實(shí)現(xiàn);
下圖從SLF4J官網(wǎng)中找到的一個(gè)圖,表示了各種實(shí)現(xiàn)類和SLF4J的關(guān)系:
?
總之,SLF4J接口及其各種適配器非常簡單,不依賴任何類加載器,所以SLF4J不會遇到類加載器問題或Commons Logging(JCL)所觀察??到的內(nèi)存泄漏。實(shí)際上,每個(gè)SLF4J綁定在編譯時(shí)都進(jìn)行了硬連線,以使用一個(gè)且僅一個(gè)特定的日志記錄框架。
靜態(tài)綁定原理
和Apache Common Logging不同,SLF4j采用了靜態(tài)綁定來確定具體日志庫。靜態(tài)綁定就是:
- 每一個(gè)具體的日志庫定義一個(gè)包名和類名都相同的類: org.slf4j.impl.StaticLoggerBinder,這個(gè)類的功能就是調(diào)用具體的日志庫,該類存放在Adaptation layer(適配層)或者native implementation of slf4j-api(實(shí)現(xiàn)包)的jar包中;(該類在slf4j-api打成jar包時(shí)被mvn移除)
- SLF4j的使用者只要把具體日志庫對應(yīng)的Adaptation layer或者native implementation of slf4j-api的jar包放入classpath中,SLF4j便會裝載(load)對應(yīng)版本的org.slf4j.impl.StaticLoggerBinder,從而調(diào)用具體的日志庫;
- slf4j-api.jar中通過classLoader.getResources("org/slf4j/impl/StaticLoggerBinder.class")來加載classpath中具體的日志庫中的StaticLoggerBinder類;
SLF4J相比JCL的一大優(yōu)勢是采用了靜態(tài)綁定,避免了在OSGI等場景中通過classloader動態(tài)綁定造成的困擾。
參考:https://blog.csdn.net/weixin_34248023/article/details/91891106
1、1.7.25版本的slf4j-api.jar靜態(tài)綁定過程分析:
1.1)源碼分析:
在demo中可知,使用SLF4J的LoggerFactory.getLogger(Class<?>)方法獲取一個(gè)Logger對象,這個(gè)過程完成了和具體日志實(shí)現(xiàn)類的綁定。通過slf4j-api.jar源碼,SLF4J是調(diào)用bind()方法實(shí)現(xiàn)的綁定。
1)bind()方法:
?
2)findPossibleStaticLoggerBinderPathSet()方法:
通過jdk提供的ClassLoader.getStstemResources()方法獲取指定資源的URI。
?
可以發(fā)現(xiàn),在slf4j-api.jar包中根本沒有org.slf4j.impl.StaticLoggerBinder 這個(gè)類,所以,如果沒有具體的日志實(shí)現(xiàn)庫,那么在執(zhí)行到StaticLoggerBinder.getSingleton()方法時(shí)就會拋出NoClassDeffoundException
?
3)日志實(shí)現(xiàn)庫:
slf4j-log4j12庫中的org.slf4j.impl.StaticLoggerBinder
1.2)疑問:
通過slf4j源碼,LoggerFactory.java文件有一行import org.slf4j.impl.StaticLoggerBinder; 但是上面我們發(fā)現(xiàn)在slf4j-api.jar中居然沒有該org.slf4j.impl.StaticLoggerBinder類,也就是說slf4j-api這個(gè)工程是無法編譯通過的,又是如何打成slf4j-api.jar的呢?
寫一個(gè)工程A,類似sfl4j-api,然后把StaticLoggerBinder類刪掉,工程雖然報(bào)錯(cuò),但是可以通過mvn install打包成功;
寫一個(gè)工程B,引入A.jar,然后調(diào)用其中方法,會發(fā)現(xiàn)報(bào)錯(cuò):Unresolved compilation problem: 從A.jar包中查看相應(yīng)的LoggerFatory類,居然是這樣的:
可以發(fā)現(xiàn):雖然上面可以用mvn打包成功,但是由于A工程是一個(gè)編譯有問題的工程,反編譯字節(jié)碼文件可以看到方法全都拋出異常,這說明在打包時(shí),LoggerFactory類生成的字節(jié)碼文件是不完整的,帶有錯(cuò)誤的。
通過slf4j-api源碼可以發(fā)現(xiàn),其實(shí)在slf4j-api工程中是有org.slf4j.impl.StaticLoggerBinder.java類的,只是在mvn打包的時(shí)候通過ant插件,將org.slf4j.impl.StaticLoggerBinder.class移除掉了。騙過了jdk,使得LoggerFactory.class是一個(gè)完整的,可以校驗(yàn)通過的字節(jié)碼文件。
1.3)總結(jié):
先來明確一下 Java 的綁定(Binding)的概念,Java 本身只支持靜態(tài)(static)綁定與運(yùn)行時(shí)(runtime)綁定,直到與 JDK 1.6 版本一起發(fā)布的 JSR269 才能進(jìn)行編譯時(shí)綁定,編譯時(shí)綁定最有代表的是lomok 在編譯過程中修改字節(jié)碼。
1.7.25版本的SFL4j 的 logger 實(shí)例是 new 出來的(通過StaticLoggerBinder單例),綁定 LogContext 的 StaticLoggerBinder(中介類) 是寫死的,編譯時(shí)并沒有處理任何邏輯,也談不上什么編譯時(shí)綁定,而且翻遍了 SLF4j 文檔也沒有找到任何有關(guān)編譯時(shí)綁定的材料,官方只提到了 “static binding”, 所以,SLF4j使用的是 Convention over Configuration(CoC)– 慣例優(yōu)于配置原則,不管是什么日志框架,只加載org.slf4j.impl.StaticLoggerBinder。這完美契合了軟件設(shè)計(jì)的 KISS(Keep It Simple, Stupid)原則。
而 Commons-logging 魔法(magic)一樣的動態(tài)加載雖然設(shè)計(jì)很高大上,在應(yīng)用領(lǐng)域卻直接被打臉,低效率、與 OSGi 共同使用所導(dǎo)致的 ClassLoader 問題更是火上澆油,所以員外與大家共勉,寫代碼切勿炫技。
參考:
https://juejin.im/post/6844903574116237326
https://www.jianshu.com/p/b562b7ff499f
2、1.8版本的slf4j-api.jar靜態(tài)綁定過程分析:
SLF4J 1.8中最大的改進(jìn)就是摒棄了之前的hard code的代碼綁定(要求具體實(shí)現(xiàn)日志框架中必須要有一個(gè)org.slf4j.impl.StaticLoggerBinder.java),而是使用了更加優(yōu)雅、耦合更松的SPI方式進(jìn)行服務(wù)發(fā)現(xiàn)。我們看看1.8版本slf4j-api中對日志綁定的改進(jìn):
- 提供了org.slf4j.spi.SLF4JServiceProvider服務(wù)接口用于SPI綁定
- 改進(jìn)了org.slf4j.LoggerFactory.bind()的實(shí)現(xiàn),采用SPI方式進(jìn)行SLF4JServiceProvider服務(wù)發(fā)現(xiàn)和綁定
- 不再支持1.8版本以前的按照約定的類型StaticXxxBinder約定類名進(jìn)行綁定的方式
由此可見,1.8版本和之前的版本是不兼容的(http://www.slf4j.org/codes.html#version_mismatch)。而且1.8往上的版本都是beta,沒有一個(gè)是stable/release的。
說明:(官網(wǎng))
從客戶端的角度來看,slf4j-api的所有版本都是兼容的。只需要確保綁定的版本與slf4j-api.jar的版本匹配即可。在初始化時(shí),如果SLF4J懷疑可能存在sfl4j-api與綁定版本不匹配的問題,它將發(fā)出有關(guān)可疑不匹配的警告。
1.1)源碼分析:
1)bind方法:
2)findServiceProviders()方法:
3)日志實(shí)現(xiàn)庫:
slf4j-api:1.8.0-beta-2版本,對應(yīng)的logback-classic版本為logback-classic:1.3.0-alpha4。為了兼容1.8的SLF4J,logback-classic提供了SPI服務(wù)配置文件,如下圖。這樣,在啟動階段,SLF4J就可以通過ServiceLoader找到logback-classic并進(jìn)行注冊了。
同時(shí),最新版的logback也去掉了org.slf.impl包,徹底摒棄了老版本SLF4J的支持。
同樣,在slf4j-log4j12-1.8版本中,也是去掉了org.slf.impl包,提供了SPI服務(wù)配置文件:
總結(jié):
slf4j-api1.8版本整個(gè)流程和1.7的基本一致,除了采用了更優(yōu)雅的服務(wù)發(fā)現(xiàn)機(jī)制,在其他方面,SLF4J 1.8與之前版本差別很小。
參考:
https://www.jianshu.com/p/6cf21fb18639
?
總結(jié)
- 上一篇: 线上培训!如何添加自定义形态选股策略!股
- 下一篇: LIS系统源码 实验室信息管理系统源码