天天看點

commons-logging + log4j源碼分析

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

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的問題,但在使用時要引入各種橋接包,如果引入了兩個相反的橋接包就會導緻循環依賴的***