commons-logging + log4j源碼分析

  • 2019 年 10 月 3 日
  • 筆記

分析之前先理清楚幾個概念

Log4J = Log For Java

SLF4J = Simple Logging Facade for Java

看到Facade首先想到的就是設計模式中的門面(Facade)模式,實際上SLF4J 就是一個裝”門面”的java日誌框架,它只提供一層抽象且通用的日誌API供調用方寫日誌使用,而真正實現寫日誌功能的則是Log4J、logback等框架和從jdk1.4之後開始提供的java.util.logging包,而具體要使用誰就要看SLF4J中設置的策略,整體來看的話也確實是使用了門面模式

關於這幾個日誌框架的誕生和關係推薦看下這篇部落格:https://blog.csdn.net/qq_32625839/article/details/80893550

 

Apache的commons-logging和SLF4J 一樣,也是一個抽象的日誌框架,使用得更廣泛,下面通過幾段源碼分析下其內部的門面模式是怎樣實現的

一般寫日誌之前都要先用下面的方法獲取到log對象

Log log = LogFactory.getLog(clz.getName());

進入getLog方法
    public static Log getLog(String name) throws LogConfigurationException {          return getFactory().getInstance(name);      }

看來是先獲取到log工廠對象再獲取到log對象,進入getFactory方法

//獲取到上下文中的類載入器
ClassLoader contextClassLoader = getContextClassLoaderInternal();
//先嘗試從快取中獲取LogFactory
LogFactory factory = getCachedFactory(contextClassLoader); if (factory != null) {   return factory; }

  //如果存在commons-logging.properties配置文件且其中的use_tccl配置項為false,則使用thisClassLoader作為後續的類載入器,thisClassLoader就是當前類的載入器
  Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
  ClassLoader baseClassLoader = contextClassLoader;
  if (props != null) {
    String useTCCLStr = props.getProperty(TCCL_KEY);
    if (useTCCLStr != null) {
      if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
        baseClassLoader = thisClassLoader;
      }
    }
  }

//獲取org.apache.commons.logging.LogFactory系統配置項,若存在則使用配置的類新建一個工廠對象
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
}

//利用java中的SPI機制根據META-INF/services/org.apache.commons.logging.LogFactory中的配置類新建工廠對象
if (factory == null) {
try {
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
     ...
     factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
     ...

//根據commons-logging.properties中的配置屬性org.apache.commons.logging.LogFactory新建工廠對象

  if (factory == null) {
    String factoryClass = props.getProperty(FACTORY_PROPERTY);
    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);

  //如果以上都沒成功創建工廠對象則直接使用默認的org.apache.commons.logging.impl.LogFactoryImpl類創建

factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);

//最後放入快取

cacheFactory(contextClassLoader, factory);

一般上述過程中屬性配置都不加的話默認的logFactory實現類就是LogFactoryImpl,然後進入到它的getInstance方法看下

 

public Log getInstance(String name) throws LogConfigurationException {          Log instance = (Log) instances.get(name);          if (instance == null) {              instance = newInstance(name);              instances.put(name, instance);          }          return instance;      }

進入newInstance

    protected Log newInstance(String name) throws LogConfigurationException {          Log instance;          try {              if (logConstructor == null) {                  instance = discoverLogImplementation(name);              }              else {                  Object params[] = { name };                  instance = (Log) logConstructor.newInstance(params);              }                if (logMethod != null) {                  Object params[] = { this };                  logMethod.invoke(instance, params);              }                return instance;

進入discoverLogImplementation

    private Log discoverLogImplementation(String logCategory)          throws LogConfigurationException {          initConfiguration();          Log result = null;

    //
findUserSpecifiedLogClassName方法內部也是讀取一些系統配置項,如果讀取到了就根據配置的類名來創建日誌對象
        String specifiedLogClassName = findUserSpecifiedLogClassName();          if (specifiedLogClassName != null) {              result = createLogFromClass(specifiedLogClassName,logCategory,true);              if (result == null) {                  StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");                  messageBuffer.append(specifiedLogClassName);                  messageBuffer.append("' cannot be found or is not useable.");                  informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);                  informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);                  informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);                  informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);                  throw new LogConfigurationException(messageBuffer.toString());              }              return result;          }
     //如果上面沒有創建成功則根據classesToDiscover數組的值作為類名依次創建日誌對象,直到創建成功(不為空)就返回
     for(int i=0; i<classesToDiscover.length && result == null; ++i) { result = createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException ("No suitable Log implementation"); } return result; }

其中classesToDiscover數組的值是寫死的

private static final String[] classesToDiscover = {
  ”org.apache.commons.logging.impl.Log4JLogger”,
  ”org.apache.commons.logging.impl.Jdk14Logger”,
  ”org.apache.commons.logging.impl.Jdk13LumberjackLogger”,
  ”org.apache.commons.logging.impl.SimpleLog”
}

所以當我們的項目中引入的commons-logging和Log4j的jar包,其實不需要做任何配置,就會優先使用Log4JLogger做為實際的寫日誌實現類

整個過程中有兩點需要注意的:

1.日誌工廠對象和日誌實現類對象都是先使用當前類或者thread context的classLoad將類載入進來再通過反射創建對象,這樣的話如果有的插件使用自定義的classLoad載入,當插件內部列印日誌時可能會出現無法創建日誌對象或者使用了和預期不一致的日誌對象

2.動態查找:對日誌工廠實現類和日誌實現類的動態查找基本上都是通過讀取系統配置或者程式碼里寫死的方式來查找,其實可以通過java SPI機制來實現

slf4J的實現方式與上述是完全不同的,它採用的是靜態綁定,可以避免classLoad的問題,但在使用時要引入各種橋接包,如果引入了兩個相反的橋接包就會導致循環依賴的StackOverflow