Slf4j 包衝突問題緣由與解決

1、前言

在進行 Java 開發時,一般咱們會選擇 Slf4j 做爲日誌門面,但日誌實現卻不盡相同。若是系統運行中同時存在多個日誌實現,就會出現相似下圖的 Warning。web

2、問題緣由

咱們知道 SpringBoot 默認使用的日誌實現是 Logback,所以咱們嘗試在項目中引入 Log4j 的依賴時,就復現了上圖的報錯。spring

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
複製代碼

上圖報錯告知咱們存在多個 SLF4J bingdings,分別位於 logback 和 log4j 包中,有兩個 StaticLoggerBinder。編程

咱們知道使用 Slf4j ,須要 LoggerFactory.getLogger() 方法獲取實例。markdown

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private final Logger logs = LoggerFactory.getLogger(xxx.class);
複製代碼

咱們就能夠經過這個做爲入口,去看看源碼的實現。以下圖所示,我標註了須要關注的核心代碼。框架

  • (1)調用 getILoggerFactory() 方法獲得 LoggerFactory。
  • (2)對於首次調用,INITIALIZATION_STATE 應該是 UNINITIALIZED,因此進入初始化的邏輯,調用方法 performInitialization()。
  • (3)調用 bind() 方法。
  • (4)若是不是 isAndroid(),調用 findPossibleStaticLoggerBinderPathSet() 方法,故名思意,查找可能的 staticLoggerBinder,注意這裏返回的類型是 SET,便可能是多個。
  • (5)在findPossibleStaticLoggerBinderPathSet() 這個方法內,首先經過 classLoader 加載了 org/slf4j/impl/StaticLoggerBinder.class 這個類的 path,它可能存在多個,所以使用了 while 獲取了全部的 path,並最終返回。

  • (6)reportActualBinding() 方法會校驗 SET 的 size,若是大於 1,就會打印出一開始咱們看見的 Warning 了。

3、問題解決

解決思路就是將你不想要的日誌實現從依賴包中排除掉便可,經過 IDEA 提供的 Diagrams 可以很是方便的查看項目中的依賴關係。spring-boot

打開項目的 POM 文件,右鍵選擇 Diagrams -> Show Dependenciesspa

假設咱們想要排除 logback 依賴,使用 log4j。Ctrl + F 搜索 logback,能夠找到引用該依賴的樹形結構。日誌

點擊窗口左上角的下圖中的這個圖標,能夠只看當前選中的這個依賴的關係。code

選中後效果以下:orm

如上圖所示,logback 由 spring-boot-starter-logging 引入,最頂層是由 spring-boot-starter-web 和 spring-boot-starter-test 引入。

咱們嘗試在 spring-boot-starter-web 中排除該依賴,應該就能夠了。若是排出後從新搜索仍然存在 logback 依賴,則重複執行排除的操做。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>` 
複製代碼

4、總結

日誌框架衝突特別對於新手來講處理起來比較頭疼,由於涉及到了日誌接口和日誌實現。

咱們推崇的應該是面向接口編程,所以咱們大到開源項目,小到公司的公共 jar 包,應當合理利用 Maven 的傳遞機制。具體的日誌實現不該該傳遞出去,避免影響到調用的下游方。

<optional>true</optional>` 
複製代碼