您好,登錄后才能下訂單哦!
slf4j中橋接器運行的原理是什么?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
在日志框架 slf4j 中有一組項目,除了核心的 slf4j-api 之外,還有 slf4j-log4j12、slf4j-jdk14 等項目。這一類項目統稱橋接器項目,針對不同的日志框架有不同的橋接器項目。
在使用 logback 日志框架時,并沒有針對的橋接器,這是因為 logback 與 slf4j 是一個作者所寫,在 logback 中直接實現了 slf4j 的 SPI 機制。
但如果使用其他日志框架,那么就必須要用到橋機器相關依賴。比如,當我們基于 log4j 使用 slf4j 時,除了需要引入 log4j 的 jar 包依賴,還需要引入 slf4j 的下面兩個依賴:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency>
slf4j-api 為核心依賴,必須引入,而 slf4j-log4j12 就是橋接器用來在 slf4j 和 log4j 之間進行過渡和封裝。下面,我們就聊聊橋接器項目的核心實現。
slf4j-log4j12 橋接器的價值
要了解橋接器的運作,首先需要回顧一下 slf4j 的 SPI 機制。在我們通過 LoggerFactory.getLogger(Foo.class); 時,slf4j 會通過 SPI 機制尋找并初始化 SLF4JServiceProvider 的實現類。
然后,通過 SLF4JServiceProvider 的實現類來獲取日志相關的具體工廠類對象,進而進行日志功能的處理。先來看一下 SLF4JServiceProvider 的接口定義:
public interface SLF4JServiceProvider { /** * 返回ILoggerFactory的實現類,用于LoggerFactory類的綁定 */ ILoggerFactory getLoggerFactory(); /** * 返回IMarkerFactory實例 */ IMarkerFactory getMarkerFactory(); /** * 返回MDCAdapter實例 */ MDCAdapter getMDCAdapter(); /** * 獲取請求版本 */ String getRequesteApiVersion(); /** * 初始化,實現類中一般用于初始化ILoggerFactory等 */ void initialize(); }
SLF4JServiceProvider 接口是在 slf4j-api 中定義的,具體的實現類由其他日志框架來完成。但是像 log4j(logback“敵對陣營”)是不會在框架內實現該接口的。那么,怎么辦?
針對此問題,slf4j 提供了 slf4j-log4j12 這類橋接器的過渡項目。在其中實現 SLF4JServiceProvider 接口,并對 Log4j 日志框架接口進行封裝,將 Logger(slf4j) 接收到的命令全部委托給 Logger(log4j) 去完成,在使用者無感知的情況下完成偷天換日。
slf4j-log4j12 的核心實現類
理解了橋接器的存在價值及原理,下面就來看看 slf4j-log4j12 是如何實現這一功能的。
首先來看看核心實現類之一 Log4j12ServiceProvider。它實現了 SLF4JServiceProvider 接口,主要功能就是完成接口中定義的相關工廠接口的實現。源代碼如下:
public class Log4j12ServiceProvider implements SLF4JServiceProvider { public static String REQUESTED_API_VERSION = "1.8.99"; private ILoggerFactory loggerFactory; private IMarkerFactory markerFactory; private MDCAdapter mdcAdapter; public Log4j12ServiceProvider() { try { @SuppressWarnings("unused") Level level = Level.TRACE; } catch (NoSuchFieldError nsfe) { Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version"); } } @Override public void initialize() { loggerFactory = new Log4jLoggerFactory(); markerFactory = new BasicMarkerFactory(); mdcAdapter = new Log4jMDCAdapter(); } @Override public ILoggerFactory getLoggerFactory() { return loggerFactory; } @Override public IMarkerFactory getMarkerFactory() { return markerFactory; } @Override public MDCAdapter getMDCAdapter() { return mdcAdapter; } @Override public String getRequesteApiVersion() { return REQUESTED_API_VERSION; } }
該類的實現看起來很簡單,構造方法中通過嘗試使用 log4j 的 Level.TRACE 調用來驗證 log4j 的版本是否符合要求。log4j1.2.12 之前并沒有 Level.TRACE,所以會拋出異常,并打印日志信息。不得不贊嘆作者在此處檢查版本的巧妙用法。
而這里對接口中返回的實現類主要通過 initialize() 方法來實現的。這里我們重點看 Log4jLoggerFactory 類的實現。
public class Log4jLoggerFactory implements ILoggerFactory { private static final String LOG4J_DELEGATION_LOOP_URL = "http://www.slf4j.org/codes.html#log4jDelegationLoop"; // check for delegation loops static { try { Class.forName("org.apache.log4j.Log4jLoggerFactory"); String part1 = "Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. "; String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details."; Util.report(part1); Util.report(part2); throw new IllegalStateException(part1 + part2); } catch (ClassNotFoundException e) { // this is the good case } } ConcurrentMap<String, Logger> loggerMap; public Log4jLoggerFactory() { loggerMap = new ConcurrentHashMap<>(); // force log4j to initialize org.apache.log4j.LogManager.getRootLogger(); } @Override public Logger getLogger(String name) { Logger slf4jLogger = loggerMap.get(name); if(slf4jLogger != null) { return slf4jLogger; } else { org.apache.log4j.Logger log4jLogger; if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) { log4jLogger = LogManager.getRootLogger(); } else { log4jLogger = LogManager.getLogger(name); } Logger newInstance = new Log4jLoggerAdapter(log4jLogger); Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } } }
在 Log4j12ServiceProvider 中進行了 Log4jLoggerFactory 的實例化操作,也就直接 new 出來一個對象。我們知道,在 new 對象執行會先執行 static 代碼塊,本類的靜態代碼塊的核心工作就是檢查依賴文件中是否同時存在反向橋接器的依賴。
其中,org.apache.log4j.Log4jLoggerFactory 是反向橋接器 log4j-over-slf4j 項目中的類,如果加裝到了,說明存在,則拋出異常,打印日志信息。此處再次贊嘆作者運用的技巧的巧妙。
在 Log4jLoggerFactory 的構造方法中,做了兩件事:第一,初始化一個 ConcurrentMap 變量,用于存儲實例化的 Logger;第二,強制初始化 log4j 的組件,其中強制初始化 log4j 的組件是通過 getRootLogger 方法,來初始化一些靜態的變量。
構造方法時初始化了 ConcurrentMap 變量,在 Log4jLoggerFactory 實現的 getLogger 方法中,先從 Map 中獲取一下是否存在對應的 Logger,如果存在直接返回,如果不存在則進行構造。而構造的 Log4jLoggerAdapter 類很顯然使用了適配器模式,它內部持有了 log4j 的 Logger 對象,自身又實現了 slf4j 的 Logger 接口。
下面看一下 Log4jLoggerAdapter 的部分代碼實現:
public final class Log4jLoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger, Serializable { final transient org.apache.log4j.Logger logger; Log4jLoggerAdapter(org.apache.log4j.Logger logger) { this.logger = logger; this.name = logger.getName(); traceCapable = isTraceCapable(); } @Override public boolean isDebugEnabled() { return logger.isDebugEnabled(); } @Override public void log(Marker marker, String callerFQCN, int level, String msg, Object[] arguments, Throwable t) { Level log4jLevel = toLog4jLevel(level); NormalizedParameters np = NormalizedParameters.normalize(msg, arguments, t); String formattedMessage = MessageFormatter.basicArrayFormat(np.getMessage(), np.getArguments()); logger.log(callerFQCN, log4jLevel, formattedMessage, np.getThrowable()); } public void log(LoggingEvent event) { Level log4jLevel = toLog4jLevel(event.getLevel().toInt()); if (!logger.isEnabledFor(log4jLevel)) return; org.apache.log4j.spi.LoggingEvent log4jevent = toLog4jEvent(event, log4jLevel); logger.callAppenders(log4jevent); } // 省略其他方法 }
源碼中,通過構造方法傳入 log4j 的 Logger 對象,而 Log4jLoggerAdapter 對外提供的方法,都是通過 log4j 的 Logger 進行具體實現。
總之,slf4j 的 Logger 接口的方法通過 Log4jLoggerAdapter 進行包裝和轉換,交由 log4j 的 Logger 去執行,這就達到了連接 slf4j-api 和 log4j 的目的。而此時,slf4j-api 不并關系日志是如何實現記錄,對此也無感知。
看完上述內容,你們掌握slf4j中橋接器運行的原理是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。