主頁 > 後端開發 > SLF4J門面日志框架原始碼探索

SLF4J門面日志框架原始碼探索

2023-06-22 07:38:23 後端開發

1 SLF4J介紹

SLF4J即Simple Logging Facade for Java,它提供了Java中所有日志框架的簡單外觀或抽象,因此,它使用戶能夠使用單個依賴項處理任何日志框架,例如:Log4j,Logback和JUL(java.util.logging),通過在類路徑中插入適當的 jar 檔案(系結),可以在部署時插入所需的日志框架,如果要更換日志框架,僅僅替換依賴的slf4j bindings,比如,從java.util.logging替換為log4j,僅僅需要用slf4j-log4j12-1.7.28.jar替換slf4j-jdk14-1.7.28.jar,

2 SLF4J原始碼分析

我們通過代碼入手,層層加碼,直觀感受SLF4J列印日志,并跟蹤代碼追本溯源,主要了解,SLF4J是如何作為門面和其他日志框架進行解耦,

2.1 pom只參考依賴slf4j-api,版本是1.7.30

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>

2.1.1 執行一個Demo

public class HelloSlf4j {

    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(HelloSlf4j.class);
        logger.info("Hello World info");
    }
}

2.1.2 日志提示資訊

系結org.slf4j.impl.StaticLoggerBinder失敗,如果在類路徑上沒有找到系結,那么 SL??F4J 將默認為無操作實作

2.1.3 跟蹤原始碼

點開方法getLogger(),可以直觀看到LoggerFactory使用靜態工廠創建Logger,通過以下方法,逐步點擊,報錯也很容易找到,可以在bind()方法看到列印的例外日志資訊,

org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
org.slf4j.LoggerFactory#getLogger(java.lang.String)
org.slf4j.LoggerFactory#getILoggerFactory
org.slf4j.LoggerFactory#performInitialization
org.slf4j.LoggerFactory#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()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class "org.slf4j.impl.StaticLoggerBinder".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        } finally {
            postBindCleanUp();
        }
    }

進一步分析系結方法findPossibleStaticLoggerBinderPathSet(),可以發現在當前ClassPath下查詢了所有該路徑的資源“org/slf4j/impl/StaticLoggerBinder.class”,這里可能沒有加載到任何檔案,也可能系結多個,對沒有系結和系結多個的場景進行了友好提示,這里通過路徑加載資源的目的主要用來對加載的各種例外場景提示,

再往下代碼StaticLoggerBinder.getSingleton()才是實際的系結,并且獲取StaticLoggerBinder的實體,這里如果反編譯,你會發現根本沒有這個類StaticLoggerBinder,

如果沒有加載到檔案,正如上邊demo執行的結果一樣,命中NoSuchMethodError例外,并列印沒有系結場景的提示資訊,

方法findPossibleStaticLoggerBinderPathSet()的原始碼如下,可以發現類加載器通過路徑獲取URL資源,

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

2.2 pom參考依賴logback-classic

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

2.2.1 執行demo

可以看到正常的列印日志資訊,并且沒有任何例外

2.2.2 跟蹤原始碼

這個時候如果再點擊進入方法StaticLoggerBinder.getSingleton(),發現類StaticLoggerBinder是由包logback-classic提供的,并且實作了SLF4J中的介面LoggerFactoryBinder,StaticLoggerBinder的創建用到了單例模式,該類主要目的回傳一個創建Logger的工廠,這里實際回傳了ch.qos.logback.classic.LoggerContext的實體,再由該實體創建ch.qos.logback.classic.Logger,

UML類圖如下:

2.3 pom再引入log4j-slf4j-impl

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>

2.3.1 執行demo

列印日志如下,提示系結了兩個StaticLoggerBinder.class,但最終實際系結的是ch.qos.logback.classic.util.ContextSelectorStaticBinder,這里邊也驗證了一旦一個類被加載之后,全域限定名相同的類就無法被加載了,這里Jar包被加載的順序直接決定了類加載的順序,

SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
18:19:43.521 [main] INFO com.cj.HelloSlf4j - Hello World info

2.4 log4j-slf4j-impl和logback-classic的引入位置變換

如果Pom檔案先引入log4j-slf4j-impl,再引入logback-classic

2.4.1 執行demo

根據日志列印結果,可以看到實際系結的是org.apache.logging.slf4j.Log4jLoggerFactory;但是沒有正常列印出日志,需要進行log4j2的日志配置,說明實際系結的是og4j-slf4j-impl包中的org/slf4j/impl/StaticLoggerBinder.class檔案;這里也驗證了如果有引入了多個橋接包,實際系結的是先加載到的檔案;

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging.

2.5 類加載方式的變化

2.5.1 slf4j-api-1.7.30版本的打包技巧

反編譯看slf4j-api-1.7.30-sources.jar,發現壓根沒有這個類org.slf4j.impl.StaticLoggerBinder,他怎么會編譯成功呢?猜想是不是打包的時候把這個類排除掉了呢?通過git下載原始碼發現slf4j原始碼其實是有這個檔案的,org/slf4j/impl/StaticLoggerBinder.class;這里使用了一個小技巧,打包的時候把實作類排除掉了,雖然不太優雅,但是思路很巧妙,

2.5.2 slf4j-api-2.0.0版本引入SPI(Service Provider Interface)

該版本通過使用SPI方式進行實作類的加載,感覺比之前的實作方式優雅了很多,橋接包只需要在這個位置:META-INF/services/,定義一個檔案org.slf4j.spi.SLF4JServiceProvider(命名為SLFJ4提供的介面名),并且檔案中指定實作類,只要引入這個橋接包,就可以適配到對應實作的日志框架,

以下是SPI方式加載的原始碼

private static List<SLF4JServiceProvider> findServiceProviders() {
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList();
        Iterator var2 = serviceLoader.iterator();

        while(var2.hasNext()) {
            SLF4JServiceProvider provider = (SLF4JServiceProvider)var2.next();
            providerList.add(provider);
        }

        return providerList;
     }

2.5.3 類加載方式對比

2.6 SLF4J官方已經實作系結的日志框架

slf4j已經提供了常用日志框架的橋接包,以及詳細的檔案描述,使用起來非常簡單,
下圖是SLF4J官網中提供的,表示了各種日志實作框架和SLF4J的關系:

2.7 總結

  • SLF4J API旨在一次系結一個且僅一個底層日志框架,而且引入SLF4J后,不管是否可以加載到StaticLoggerBinder,或者加載到多個StaticLoggerBinder,都進行友好提示,用戶體驗上考慮都很周到,如果類路徑上存在多個系結,SLF4J 將發出警告,列出這些系結的位置,當類路徑上有多個系結可用時,應該選擇一個希望使用的系結,然后洗掉其他系結,
  • 單純看SLF4J原始碼,其實整體設計實作上都很簡單明確,定位非常清楚,就是做好門面,
  • 鑒于 SLF4J 介面及其部署模型的簡單性,新日志框架的開發人員應該會發現撰寫 SLF4J 系結非常容易,
  • 對于目前比較主流的日志框架都通過實作適配進行兼容支持,只要用戶選擇了SLF4J,就可以確保以后變更日志框架的自由,

3 SLF4J設計模式的使用

在slf4j中用到了一些經典的設計模式,比如門面模式、單例模式、靜態工廠模式等,我們來分析以下幾種設計模式,

3.1 門面模式(Facade Pattern)

1)解釋

門面模式,也叫外觀模式,要求一個子系統的外部與其內部的通信必須通過一個統一的物件進行,門面模式提供一個高層次的介面,使得子系統更易于使用,使用了門面模式,使客戶端呼叫變得更加簡單,

Slf4j制定了log日志的使用標準,提供了高層次的介面, 我們編碼程序只需要依賴介面Logger和工廠類 LoggerFactory就可以實作日志的列印,完全不用關心日志內部的實作細節是logback實作的方式,還是log4j的實作方式,

2)圖解

        Logger logger = LoggerFactory.getLogger(HelloSlf4j.class);
        logger.info("Hello World info");

3)優點

解耦,減少系統的相互依賴,所有的依賴都是對門面物件的依賴,與子系統無關,業務層的開發不需要關心底層日志框架的實作及細節,在編碼的時候也不需要考慮日后更換框架所帶來的成本,
介面和實作分離,屏蔽了底層的實作細節,面向介面編程,

3.2 單例模式(Singleton Pattern)

1)解釋

單例模式,確保一個類僅有一個實體,并提供一個訪問它的全域訪問點,
在SLF4J的適配包中都需要實作類StaticLoggerBinder,而類StaticLoggerBinder的實作就用了單例模式,而且是最簡單的實作方法,在靜態初始化器中直接new StaticLoggerBinder(),提供全域訪問方法獲取該實體,

2)UML圖

3)優點

在單例模式中,活動的單例只有一個實體,對單例類的所有實體化得到的都是相同的一個實體,這樣就防止其它物件對自己的實體化,確保所有的物件都訪問一個實體
單例模式具有一定的伸縮性,類自己來控制實體化行程,類就在改變實體化行程上有相應的伸縮性,

提供了對唯一實體的受控訪問,

在記憶體里只有一個實體,減少了記憶體的開銷,提高系統的性能,

4 啟示

  • 盡管SLF4J整體代碼短小但很精煉,可見門面模式運用好的威力,門面模式也為我們提供了對于多版本的實作如何統一定義介面以及兼容提供了參考,
  • SLF4J定義和實作方案對用戶都很友好,同時又提供了各種橋接包,進行完善的檔案指導使用,總之各項用戶體驗都很棒,這也許也是SLF4J目前最受歡迎的原因之一吧,
  • 我們要多思考面向介面編程的思想,降低代碼耦合度,提高代碼擴展性,
  • 使用SPI的方式,優雅的加載擴展實作,
  • 好產品是設計出來的,更是優化迭代出來的,

5 參考資料

  • slf4j官網:https://www.slf4j.org/manual.html
    類加載:
  • https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html
  • https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html
  • https://www.ibm.com/docs/en/sdk-java-technology/7.1?topic=cl-parent-delegation-model-1

作者:京東物流 曹俊

來源:京東云開發者社區

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555740.html

標籤:其他

上一篇:Rust語言 - 介面設計的建議之受約束(Constrained)

下一篇:返回列表

標籤雲
其他(161428) Python(38244) JavaScript(25512) Java(18251) C(15238) 區塊鏈(8271) C#(7972) AI(7469) 爪哇(7425) MySQL(7260) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4606) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2436) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1970) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • SLF4J門面日志框架原始碼探索

    我們通過代碼入手,層層加碼,直觀感受SLF4J列印日志,并跟蹤代碼追本溯源。主要了解,SLF4J是如何作為門面和其他日志框架進行解耦。 ......

    uj5u.com 2023-06-22 07:38:23 more
  • Rust語言 - 介面設計的建議之受約束(Constrained)

    # Rust語言 - 介面設計的建議之受約束(Constrained) - [Rust API 指南 GitHub](https://github.com/rust-lang/api-guidelines):https://github.com/rust-lang/api-guidelines - ......

    uj5u.com 2023-06-22 07:38:19 more
  • Scala泛型

    # 泛型的定義 ```Scala object _11_泛型 { def main(args: Array[String]): Unit = { //[A] 這個代表的就是泛型 ==》 在創建物件的時候,可以指定需要傳進去的型別 //作用就是在創建物件的時候,可以對傳進去的引數一個約束,當設定泛型位 ......

    uj5u.com 2023-06-22 07:37:55 more
  • celery筆記五之訊息佇列的介紹

    > 本文首發于公眾號:Hunter后端 > 原文鏈接:[celery筆記五之訊息佇列的介紹](https://mp.weixin.qq.com/s/fw7b1Gha0XpTYuCg3aZcWA) 前面我們介紹過 task 的處理方式,將 task 發送到佇列 queue,然后 worker 從 qu ......

    uj5u.com 2023-06-22 07:31:20 more
  • java~位元組碼操作Javassist

    Javassist是一個開源的Java位元組碼操作庫,它提供了一組簡單而強大的API,用于在運行時修改和生成Java位元組碼。Javassist的名稱是"Java Programming Assistant"的縮寫,它的目標是簡化對位元組碼的操作,使開發人員能夠更輕松地實作動態代碼生成和修改。 Javas ......

    uj5u.com 2023-06-21 09:03:36 more
  • java~位元組碼操作ASM

    ASM(全稱為"Objectweb ASM")是一個用于分析和轉換Java位元組碼的框架。它允許您以程式化的方式讀取、修改和生成Java類檔案,而無需直接操作Java源代碼。ASM提供了強大而靈活的工具,使您能夠對位元組碼進行細粒度的操作,包括修改現有類、生成新的類以及在類加載時對位元組碼進行增強。 AS ......

    uj5u.com 2023-06-21 09:03:30 more
  • java~位元組碼操作ASM

    ASM(全稱為"Objectweb ASM")是一個用于分析和轉換Java位元組碼的框架。它允許您以程式化的方式讀取、修改和生成Java類檔案,而無需直接操作Java源代碼。ASM提供了強大而靈活的工具,使您能夠對位元組碼進行細粒度的操作,包括修改現有類、生成新的類以及在類加載時對位元組碼進行增強。 AS ......

    uj5u.com 2023-06-21 08:57:18 more
  • 網站怎么接入微信掃碼支付?

    # 第01章-準備作業 ## 1、微信支付產品介紹 參考資料:[產品中心 - 微信支付商戶平臺 (qq.com)](https://pay.weixin.qq.com/static/product/product_index.shtml#payment_product) 付款碼支付、JSAPI支付、 ......

    uj5u.com 2023-06-21 07:55:09 more
  • C++面試八股文:static_cast了解一下?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第20面: > 面試官:C++中支持哪些型別轉換? > > 二師兄:C++支持C風格的型別轉換,并在C++11引入新的關鍵字規范了型別轉換。 > > 二師兄:C++11引入四種新的型別轉換,分別是`static_cast`、`dynamic_cast ......

    uj5u.com 2023-06-21 07:55:03 more
  • 提前預體驗阿里大模型“通義千問”的方法來了!

    隨著AI大模型的浪潮席卷全球,如今的AI技術已經顛覆了大家對傳統AI的認識,微軟更是用瀏覽器與搜索引擎上的實踐,證明了當今的AI技術具備打破行業格局的能力。 對于我們應用開發者來說,AI基建的建設與競爭是無法參與的,但在AI的應用領域依然大有可為!目前,國內各大科技公司已經陸續推出了各自的AI大模型 ......

    uj5u.com 2023-06-21 07:54:55 more