1. 程式人生 > >commons-logging日誌實現解耦

commons-logging日誌實現解耦

truct illegal 工廠 man int 行為 efault context buffered

一、需要解耦 日誌是實際應用中的一個重要部分,日誌系統也有許多開源的實現,如java.util.logging, logback, log4j系列等。 在使用日誌系統時,如果與具體的日誌實現耦合太深,如使用log4j作為日誌的實現,在每一處需要打印日誌的地方都會創建日誌實例: logger = LogManager.getLogger("instanceName"); 當由於性能或者其他方面的需求需要更換日誌實現時,如log4j升級到log4j2,就不得不替換每一處日誌實例得創建,將會是一個噩夢般的工作。 因此需要將應用與具體的日誌實現解耦。commons-logging提供了日誌實現解耦方案,當然commons-logging也提供了簡單的日誌實現。 commons-logging的官網地址為:http://commons.apache.org/proper/commons-logging/ 其中對commons-logging的介紹: The Logging package is an ultra-thin bridge between different logging implementations. A library that uses the commons-logging API can be used with any logging implementation at runtime. Commons-logging comes with support for a number of popular logging implementations, and writing adapters for others is a reasonably simple task.
二、commons-logging簡單日誌實現:
commons-logging除了提供解耦合方案外,也提供了org.apache.commons.logging.impl.SimpleLog作為簡單的日誌實現。 使用SimpleLog,需要做相應配置: 1. 配置commons-logging.properties 在classpath根路徑放置名為commons-logging.properties的文件(該路徑不可改變),內容為: org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog 2. 創建Log實例
名為配置好了之後就可以創建Log實例並直接使用: private static final Log logger = LogFactory.getLog(CommonsLogTest.class); 輸出為,默認日誌級別為INFO 技術分享 3. 輸出配置 當然,SimpleLog也可以做一些簡單的輸出配置,打開SimpleLog的源碼,可以看到在初始化時: static { // Add props from the resource simplelog.properties InputStream in = getResourceAsStream("simplelog.properties"); if(null != in) { try { simpleLogProps.load(in); in.close(); } catch(java.io.IOException e) { // ignored } } showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName); showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName); showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime); if(showDateTime) { dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat); try { dateFormatter = new SimpleDateFormat(dateTimeFormat); } catch(IllegalArgumentException e) { // If the format pattern is invalid - use the default format dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; dateFormatter = new SimpleDateFormat(dateTimeFormat); } } } 可以看到最開始試圖從simplelog.properties讀取配置,若不存在後面指定了默認配置,因此只要在classpath根路徑放置simplelog.properties文件即可控制日誌輸出行為。 可以從http://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/impl/SimpleLog.html 的關於SimpleLog的類介紹中了解配置詳情: Simple implementation of Log that sends all enabled log messages, for all defined loggers, to System.err. The following system properties are supported to configure the behavior of this logger:
  • org.apache.commons.logging.simplelog.defaultlog
    - Default logging detail level for all instances of SimpleLog. Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, defaults to "info".
  • org.apache.commons.logging.simplelog.log.xxxxx - Logging detail level for a SimpleLog instance named "xxxxx". Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, the default logging detail level is used.
  • org.apache.commons.logging.simplelog.showlogname - Set to true if you want the Log instance name to be included in output messages. Defaults to false.
  • org.apache.commons.logging.simplelog.showShortLogname - Set to true if you want the last component of the name to be included in output messages. Defaults to true.
  • org.apache.commons.logging.simplelog.showdatetime - Set to true if you want the current date and time to be included in output messages. Default is false.
  • org.apache.commons.logging.simplelog.dateTimeFormat - The date and time format to be used in the output messages. The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. If the format is not specified or is invalid, the default format is used. The default format is yyyy/MM/dd HH:mm:ss:SSS zzz.
一個規範的simpleLog配置如下: 技術分享
三、commons-logging解耦原理 commons-logging最核心有用的功能是解耦,它的SimpleLog實現性能比不上其他實現,如log4j等。 首先,日誌實例是通過LogFactory的getLog(String)方法創建的: 技術分享 LogFatory是一個抽象類,它負責加載具體的日誌實現,分析其Factory getFactory()方法: public static org.apache.commons.logging.LogFactory getFactory() throws LogConfigurationException { // Identify the class loader we will be using ClassLoader contextClassLoader = getContextClassLoaderInternal(); if (contextClassLoader == null) { // This is an odd enough situation to report about. This // output will be a nuisance on JDK1.1, as the system // classloader is null in that environment. if (isDiagnosticsEnabled()) { logDiagnostic("Context classloader is null."); } } // Return any previously registered factory for this class loader org.apache.commons.logging.LogFactory factory = getCachedFactory(contextClassLoader); if (factory != null) { return factory; } if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + objectId(contextClassLoader)); logHierarchy("[LOOKUP] ", contextClassLoader); } // Load properties file. // // If the properties file exists, then its contents are used as // "attributes" on the LogFactory implementation class. One particular // property may also control which LogFactory concrete subclass is // used, but only if other discovery mechanisms fail.. // // As the properties file (if it exists) will be used one way or // another in the end we may as well look for it first. // classpath根目錄下尋找commons-logging.properties Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); // Determine whether we will be using the thread context class loader to // load logging classes or not by checking the loaded properties file (if any). // classpath根目錄下commons-logging.properties是否配置use_tccl ClassLoader baseClassLoader = contextClassLoader; if (props != null) { String useTCCLStr = props.getProperty(TCCL_KEY); if (useTCCLStr != null) { // The Boolean.valueOf(useTCCLStr).booleanValue() formulation // is required for Java 1.2 compatibility. if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { // Don‘t use current context classloader when locating any // LogFactory or Log classes, just use the class that loaded // this abstract class. When this class is deployed in a shared // classpath of a container, it means webapps cannot deploy their // own logging implementations. It also means that it is up to the // implementation whether to load library-specific config files // from the TCCL or not. baseClassLoader = thisClassLoader; } } } // 這裏真正開始決定使用哪個factory // 首先,嘗試查找vm系統屬性org.apache.commons.logging.LogFactory,其是否指定factory // Determine which concrete LogFactory subclass to use. // First, try a global system property if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + "] to define the LogFactory subclass to use..."); } try { String factoryClass = getSystemProperty(FACTORY_PROPERTY, null); if (factoryClass != null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class ‘" + factoryClass + "‘ as specified by system property " + FACTORY_PROPERTY); } factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined."); } } } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + "]. Trying alternative implementations..."); } // ignore } catch (RuntimeException e) { // This is not consistent with the behaviour when a bad LogFactory class is // specified in a services file. // // One possible exception that can occur here is a ClassCastException when // the specified class wasn‘t castable to this LogFactory type. if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + "] as specified by a system property."); } throw e; } // 第二,嘗試使用java spi服務發現機制,載META-INF/services下尋找org.apache.commons.logging.LogFactory實現 // Second, try to find a service by using the JDK1.3 class // discovery mechanism, which involves putting a file with the name // of an interface class in the META-INF/services directory, where the // contents of the file is a single line specifying a concrete class // that implements the desired interface. if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + "] to define the LogFactory subclass to use..."); } try { // META-INF/services/org.apache.commons.logging.LogFactory, SERVICE_ID final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); if (is != null) { // This code is needed by EBCDIC and other strange systems. // It‘s a fix for bugs reported in xerces BufferedReader rd; try { rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); } catch (java.io.UnsupportedEncodingException e) { rd = new BufferedReader(new InputStreamReader(is)); } String factoryClassName = rd.readLine(); rd.close(); if (factoryClassName != null && !"".equals(factoryClassName)) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + factoryClassName + " as specified by file ‘" + SERVICE_ID + "‘ which was present in the path of the context classloader."); } factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader); } } else { // is == null if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No resource file with name ‘" + SERVICE_ID + "‘ found."); } } } catch (Exception ex) { // note: if the specified LogFactory class wasn‘t compatible with LogFactory // for some reason, a ClassCastException will be caught here, and attempts will // continue to find a compatible class. if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(ex.getMessage()) + "]. Trying alternative implementations..."); } // ignore } } // 第三,嘗試從classpath根目錄下的commons-logging.properties中查找org.apache.commons.logging.LogFactory屬性指定的factory // Third try looking into the properties file read earlier (if found) if (factory == null) { if (props != null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Looking in properties file for entry with key ‘" + FACTORY_PROPERTY + "‘ to define the LogFactory subclass to use..."); } String factoryClass = props.getProperty(FACTORY_PROPERTY); if (factoryClass != null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Properties file specifies LogFactory subclass ‘" + factoryClass + "‘"); } factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); // TODO: think about whether we need to handle exceptions from newFactory } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass."); } } } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from.."); } } } // 最後,使用後備factory實現,org.apache.commons.logging.impl.LogFactoryImpl // Fourth, try the fallback implementation class if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Loading the default LogFactory implementation ‘" + FACTORY_DEFAULT + "‘ via the same classloader that loaded this LogFactory" + " class (ie not looking in the context classloader)."); } // Note: unlike the above code which can try to load custom LogFactory // implementations via the TCCL, we don‘t try to load the default LogFactory // implementation via the context classloader because: // * that can cause problems (see comments in newFactory method) // * no-one should be customising the code of the default class // Yes, we do give up the ability for the child to ship a newer // version of the LogFactoryImpl class and have it used dynamically // by an old LogFactory class in the parent, but that isn‘t // necessarily a good idea anyway. factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); } if (factory != null) { /** * Always cache using context class loader. */ cacheFactory(contextClassLoader, factory); if (props != null) { Enumeration names = props.propertyNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); String value = props.getProperty(name); factory.setAttribute(name, value); } } } return factory; } 可以看出,抽象類LogFactory加載具體實現的步驟如下: 1. 從vm系統屬性org.apache.commons.logging.LogFactory 2. 使用SPI服務發現機制,發現org.apache.commons.logging.LogFactory的實現 3. 查找classpath根目錄commons-logging.properties的org.apache.commons.logging.LogFactory屬性是否指定factory實現 4. 使用默認factory實現,org.apache.commons.logging.impl.LogFactoryImpl LogFactory的getLog()方法返回類型是org.apache.commons.logging.Log接口,提供了從trace到fatal方法。可以確定,如果日誌實現提供者只要實現該接口,並且使用繼承自org.apache.commons.logging.LogFactory的子類創建Log,必然可以構建一個松耦合的日誌系統。
四、log4j+commons-logging解耦 使用commons-logging解耦log4j,可按照下列步驟: 1. 將log4j和commons-logging依賴放入classpath: <!--commons-logging--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> 2. 配置log4j.xml或者log4j.properties,放入類路徑根目錄; 3. 使用commons-logging得LogFactory獲取日誌實例 log4j提供了LogManager.getLogger(String) 方法創建日誌實例,但是為了日誌實現的解耦,必須使用commons-logging提供的抽象工廠LogFactory創建日誌實例: Log logger = LogFactory.getLog(String) 接下來就可以使用logger輸出日誌信息了。 log4j如何被commons-logging加載? 根據上述commons-logging解耦的原理,如果log4j是通過繼承抽象工廠org.apache.commons.logging.LogFactory來創建實現org.apache.commons.logging.Log接口的 日誌實例的,那麽就可以通過3種方法讓commons-logging加載log4j,分別是: 1. 從vm系統屬性org.apache.commons.logging.LogFactory 2. 使用SPI服務發現機制,發現org.apache.commons.logging.LogFactory的實現 3. 查找classpath根目錄commons-logging.properties的org.apache.commons.logging.LogFactory屬性是否指定factory實現 但是,查看log4j的jar包,發現在META-INF下面並沒有名為services得文件夾,更別說org.apache.commons.logging.LogFactory文件了: 技術分享 而且,Logger類也沒有實現commons-logging提供的Log接口: 技術分享 log4j中也沒有找到繼承org.apache.commons.logging.LogFactory的類。 那麽,log4j是如何被commons-logging加載的呢? 按照上述LogFactory尋找factory實現的流程,LogFactory在找不到org.apache.commons.logging.LogFactory實現時,會使用默認實現org.apache.commons.logging.impl.LogFactoryImpl。 技術分享 通過分析LogFactoryImpl的getInstance()方法,其調用以下方法獲得logger實例: 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; } catch (LogConfigurationException lce) { // this type of exception means there was a problem in discovery // and we‘ve already output diagnostics about the issue, etc.; // just pass it on throw lce; } catch (InvocationTargetException e) { // A problem occurred invoking the Constructor or Method // previously discovered Throwable c = e.getTargetException(); throw new LogConfigurationException(c == null ? e : c); } catch (Throwable t) { handleThrowable(t); // may re-throw t // A problem occurred invoking the Constructor or Method // previously discovered throw new LogConfigurationException(t); } } discoverLogImplementation方法如下: 1. 該方法首先查找用戶是否指定了日誌實現 2. classesToDiscover定義如下: private static final String[] classesToDiscover = { LOGGING_IMPL_LOG4J_LOGGER, // org.apache.commons.logging.impl.Log4JLogger "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" }; 用戶沒有指定日誌實現的情況下,按照classesToDiscover數組元素的順序,依次創建對應Log實例,直到返回成功創建的Log實例 private Log discoverLogImplementation(String logCategory) throws LogConfigurationException { if (isDiagnosticsEnabled()) { logDiagnostic("Discovering a Log implementation..."); } initConfiguration(); Log result = null; // See if the user specified the Log implementation to use // 查找用戶是否指定了日誌實現? String specifiedLogClassName = findUserSpecifiedLogClassName(); if (specifiedLogClassName != null) { if (isDiagnosticsEnabled()) { logDiagnostic("Attempting to load user-specified log class ‘" + specifiedLogClassName + "‘..."); } 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."); // Mistyping or misspelling names is a common fault. // Construct a good error message, if we can 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; } // No user specified log; try to discover what‘s on the classpath // // Note that we deliberately loop here over classesToDiscover and // expect method createLogFromClass to loop over the possible source // classloaders. The effect is: // for each discoverable log adapter // for each possible classloader // see if it works // // It appears reasonable at first glance to do the opposite: // for each possible classloader // for each discoverable log adapter // see if it works // // The latter certainly has advantages for user-installable logging // libraries such as log4j; in a webapp for example this code should // first check whether the user has provided any of the possible // logging libraries before looking in the parent classloader. // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, // and SimpleLog will always work in any JVM. So the loop would never // ever look for logging libraries in the parent classpath. Yet many // users would expect that putting log4j there would cause it to be // detected (and this is the historical JCL behaviour). So we go with // the first approach. A user that has bundled a specific logging lib // in a webapp should use a commons-logging.properties file or a // service file in META-INF to force use of that logging lib anyway, // rather than relying on discovery. if (isDiagnosticsEnabled()) { logDiagnostic( "No user-specified Log implementation; performing discovery" + " using the standard supported logging implementations..."); } // 用戶沒有指定日誌實現的情況下,按照classesToDiscover數組元素的順序,依次創建對應Log實例,直到返回成功創建的Log實例 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; } findUserSpecifiedLogClassName方法如下: 1. 該方法首先試圖獲取commons-logging.properties 中的org.apache.commons.logging.Log指定的日誌實現 2. 試圖獲取commons-logging.properties 中的org.apache.commons.logging.log指定的日誌實現 3. 試圖獲取vm系統屬性org.apache.commons.logging.Log指定的日誌實現 4. 試圖獲取vm系統屬性org.apache.commons.logging.log指定的日誌實現 5. 都沒有返回null private String findUserSpecifiedLogClassName() { if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from attribute ‘" + LOG_PROPERTY + "‘"); } // 試圖獲取commons-logging.properties 中的org.apache.commons.logging.Log指定的日誌實現 String specifiedClass = (String) getAttribute(LOG_PROPERTY); if (specifiedClass == null) { // @deprecated if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from attribute ‘" + LOG_PROPERTY_OLD + "‘"); } // 試圖獲取commons-logging.properties 中的org.apache.commons.logging.log指定的日誌實現 specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); } if (specifiedClass == null) { if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from system property ‘" + LOG_PROPERTY + "‘"); } try { // 試圖獲取vm系統屬性org.apache.commons.logging.Log指定的日誌實現 specifiedClass = getSystemProperty(LOG_PROPERTY, null); } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("No access allowed to system property ‘" + LOG_PROPERTY + "‘ - " + e.getMessage()); } } } if (specifiedClass == null) { // @deprecated if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from system property ‘" + LOG_PROPERTY_OLD + "‘"); } try { // 試圖獲取vm系統屬性org.apache.commons.logging.log指定的日誌實現 specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null); } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("No access allowed to system property ‘" + LOG_PROPERTY_OLD + "‘ - " + e.getMessage()); } } } // Remove any whitespace; it‘s never valid in a classname so its // presence just means a user mistake. As we know what they meant, // we may as well strip the spaces. if (specifiedClass != null) { specifiedClass = specifiedClass.trim(); } return specifiedClass; } 如此一來,如果像上述一樣沒有指定任何org.apache.commons.logging.Log的實現,那麽commons-logging首先使用的是 org.apache.commons.logging.impl.Log4JLogger作為Log實現,可以看到Log4jLogger是實現了Log接口的: public class Log4JLogger implements Log, Serializable; Log4jLogger則是通過組合log4j提供的org.apache.log4j.Logger,來提供trace到fatal功能。 技術分享 在log4j的jar包沒有導入到classpath之前,這個類是無法通過編譯的。 到現在應該知道了commons-logging是如何與log4j實現解耦的了,不是通過commons-logging提供的抽象工廠,而是通過 org.apache.commons.logging.impl.LogFactoryImpl加載具體的日誌實現。不得不說,在我看來這種設計極其不好,完全可以用抽象工廠提供的日誌實現加載!
五、log4j2+commons-logging解耦 log4j2是Apache推出的log4j的升級版本,在性能等各方面都做了提升。 要想使用commons-logging解耦log4j2,可按照下列步驟: 1. 導入log4j2, commons-logging, log4j-jcl依賴 其中,log4j-jcl是用來作為log4j和commons-logging的橋接的 <!--log4j2--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.7</version> </dependency> 2. 配置好log4j2依賴的配置 log4j2支持了更多的配置方式,包括xml和json格式;若不指定配置文件位置,將其放置在classpath根路徑 3. 創建日誌實例使用 log4j2也提供了自己的日誌實例創建的方法: logger = LogManager.getLogger(Log4jTwoTest.class); 為了實現解耦,需要使用commons-logging提供的抽象共產LogFactory.getLog(String)方法獲取日誌實例即可; 解耦原理: log4j-jcl提供了解耦的實現,觀察其jar包: 技術分享 該文件內容為:org.apache.logging.log4j.jcl.LogFactoryImpl 根據commons-logging抽象日誌工廠的第二步驟spi服務發現規則,抽象工廠使用org.apache.logging.log4j.jcl.LogFactoryImpl作為實際產生日誌實現的工廠。 分析LogFactoryImpl的代碼: private final LoggerAdapter<Log> adapter = new LogAdapter(); private final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>(); @Override public Log getInstance(final String name) throws LogConfigurationException { return adapter.getLogger(name); } Log實例是由LogAdapter類的getLogger(String)方法創建的,其繼承了抽象適配器AbstractLoggerAdapter<Log>的getLogger方法: public L getLogger(String name) { LoggerContext context = this.getContext(); ConcurrentMap loggers = this.getLoggersInContext(context); Object logger = loggers.get(name); if(logger != null) { return logger; } else { loggers.putIfAbsent(name, this.newLogger(name, context)); return loggers.get(name); } } 最終調用LogAdapter的newLogger()方法產生日誌實例: @Override protected Log newLogger(final String name, final LoggerContext context) { return new Log4jLog(context.getLogger(name)); } Log4jLog組合了log4j2中的ExtendedLogger提供日誌打印功能。 可以看出,log4j2和commons-logging的解耦,完全使用了commons-logging提供的spi服務發現機制。 參考: SimpleLog類介紹: http://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/impl/SimpleLog.html commons.logging1.1.1源代碼研究(2)-- 基本使用及配置文件 http://blog.csdn.net/gtuu0123/article/details/4509884 使用commons.logging中的SimpleLog顯示調試和日誌信息 http://blog.csdn.net/gxmark/article/details/7338253 Apache Commons Logging 是如何決定使用哪個日誌實現類的 http://blog.csdn.net/sunnydogzhou/article/details/5642319

commons-logging日誌實現解耦