天天看點

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);                  }              // 省略其他方法              }           

小結