转换有3种,分别是”JCL到SLF4J“,”Log4J到SLF4J“和”Java Logging API到SLF4J“。其中”Java Logging API到SLF4J“是特殊的转换,我们不能排除对JDK的依赖,而只能将”JDK_HOME/jre/lib/logging.properties“文件中的”handlers“属性值设定为”org.slf4j.bridge.SLF4JBridgeHandler“。
<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> </dependency> </dependencies>
2、日志框架配置文件
不配置”log4j.properties"文件,配置"logback.xml"文件,"logback.xml"文件内容如下:<!--配置debug=true,从而打印任何内部状态下的信息--> <configuration debug="true"> <!--配置ConsoleAppender实例--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%-5level %logger %C %M %d{MM/dd-HH:mm:ss.SSS} [%thread] - %msg%n</pattern> </encoder> </appender> <!--给名称为"ROOT"的特殊Logger实例绑定"console"这个Appender,并设置它的Level值为"debug"--> <!--在Logger实例层次结构中,该Logger实例处于最顶层,其下的子孙Logger实例会继承它的Level值和绑定的Appender,除非特别指定--> <root level="debug"> <appender-ref ref="console"/> </root> </configuration>
import org.apache.log4j.Logger; public class Main { public static void main(String[] args) { Logger logger = Logger.getLogger(Main.class); logger.error("hello world"); } }
如图1。
图1
由结果可知,转换没有成功。"org.apache.log4j.Logger"来自"log4j:log4j",最后导致尝试加载"log4j.properties"日志框架配置文件,由于没有配置而加载不到,从而出现如图所示结果。如果转换成功,“org.apache.log4j.Logger”就会来自“org.slf4j:log4j-over-slf4j”,最后会导致Logback尝试加载"logback.xml"配置文件,由于配置而加载成功。
二、JCL到SLF4J转换
1、pom.xml<dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> </dependency> </dependencies>
<!--配置debug=true,从而打印任何内部状态下的信息--> <configuration debug="true"> <!--配置ConsoleAppender实例--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%-5level %logger %C %M %d{MM/dd-HH:mm:ss.SSS} [%thread] - %msg%n</pattern> </encoder> </appender> <!--给名称为"ROOT"的特殊Logger实例绑定"console"这个Appender,并设置它的Level值为"debug"--> <!--在Logger实例层次结构中,该Logger实例处于最顶层,其下的子孙Logger实例会继承它的Level值和绑定的Appender,除非特别指定--> <root level="debug"> <appender-ref ref="console"/> </root> </configuration>
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class Main { public static void main(String[] args) { Log log = LogFactory.getLog(Main.class); log.error("hello world"); } }
4、运行结果
如图2。由结果可知,转换成功了。
图2
存在重复依赖冲突的情况下,转换成功的可能原因有两个:
1)Java类加载器加载"org.apache.commons.logging.Log"和"org.apache.commons.logging.LogFactory"这两个类的时候,正好从"org.slf4j:jcl-over-slf4j"中加载得到。此时执行“Log log = LogFactory.getLog(Main.class);”语句,中间过程中会返回一个“org.apache.commons.logging.impl.SLF4JLogFactory”类实例,该实例最后返回一个适配器类实例,该适配器类继承“org.apache.commons.logging.Log”类,其中包含对"org.slf4j.Logger"类实例的引用。
2)Java类加载器加载"org.apache.commons.logging.LogFactory"的时候,从"commons-logging:commons-logging"加载得到。此时执行"Log log = LogFactory.getLog(Main.class);"语句,中间过程中会去执行如下所示方法,去获得一个LogFactory实例。
public static LogFactory getFactory() throws LogConfigurationException { ClassLoader contextClassLoader = getContextClassLoader(); if(contextClassLoader == null && isDiagnosticsEnabled()) { logDiagnostic("Context classloader is null."); } LogFactory factory = getCachedFactory(contextClassLoader); if(factory != null) { return factory; } else { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] LogFactory implementation requested for the first time for context classloader " + objectId(contextClassLoader)); logHierarchy("[LOOKUP] ", contextClassLoader); } Properties props = getConfigurationFile(contextClassLoader, "commons-logging.properties"); ClassLoader baseClassLoader = contextClassLoader; String names; if(props != null) { names = props.getProperty("use_tccl"); if(names != null && !Boolean.valueOf(names).booleanValue()) { baseClassLoader = thisClassLoader; } } if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for system property [org.apache.commons.logging.LogFactory] to define the LogFactory subclass to use..."); } try { names = System.getProperty("org.apache.commons.logging.LogFactory"); if(names != null) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class \'" + names + "\' as specified by system property " + "org.apache.commons.logging.LogFactory"); } factory = newFactory(names, baseClassLoader, contextClassLoader); } else if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No system property [org.apache.commons.logging.LogFactory] defined."); } } catch (SecurityException var9) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] A security exception occurred while trying to create an instance of the custom factory class: [" + var9.getMessage().trim() + "]. Trying alternative implementations..."); } } catch (RuntimeException var10) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] An exception occurred while trying to create an instance of the custom factory class: [" + var10.getMessage().trim() + "] as specified by a system property."); } throw var10; } String value; if(factory == null) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for a resource file of name [META-INF/services/org.apache.commons.logging.LogFactory] to define the LogFactory subclass to use..."); } try { InputStream names1 = getResourceAsStream(contextClassLoader, "META-INF/services/org.apache.commons.logging.LogFactory"); if(names1 != null) { BufferedReader name; try { name = new BufferedReader(new InputStreamReader(names1, "UTF-8")); } catch (UnsupportedEncodingException var7) { name = new BufferedReader(new InputStreamReader(names1)); } value = name.readLine(); name.close(); if(value != null && !"".equals(value)) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + value + " as specified by file \'" + "META-INF/services/org.apache.commons.logging.LogFactory" + "\' which was present in the path of the context" + " classloader."); } factory = newFactory(value, baseClassLoader, contextClassLoader); } } else if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No resource file with name \'META-INF/services/org.apache.commons.logging.LogFactory\' found."); } } catch (Exception var8) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] A security exception occurred while trying to create an instance of the custom factory class: [" + var8.getMessage().trim() + "]. Trying alternative implementations..."); } } } if(factory == null) { if(props != null) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking in properties file for entry with key \'org.apache.commons.logging.LogFactory\' to define the LogFactory subclass to use..."); } names = props.getProperty("org.apache.commons.logging.LogFactory"); if(names != null) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Properties file specifies LogFactory subclass \'" + names + "\'"); } factory = newFactory(names, baseClassLoader, contextClassLoader); } 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.."); } } if(factory == null) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Loading the default LogFactory implementation \'org.apache.commons.logging.impl.LogFactoryImpl\' via the same classloader that loaded this LogFactory class (ie not looking in the context classloader)."); } factory = newFactory("org.apache.commons.logging.impl.LogFactoryImpl", thisClassLoader, contextClassLoader); } if(factory != null) { cacheFactory(contextClassLoader, factory); if(props != null) { Enumeration names2 = props.propertyNames(); while(names2.hasMoreElements()) { String name1 = (String)names2.nextElement(); value = props.getProperty(name1); factory.setAttribute(name1, value); } } } return factory; } }一般情况下,最后获得的LogFactory实例是"org.apache.commons.logging.impl.LogFactoryImpl"类实例,该LogFactory实例中包含了动态确定选用的日志框架的代码逻辑。
但我们也从中发现,在以上方法中包含有如下片段:
if(factory == null) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for a resource file of name [META-INF/services/org.apache.commons.logging.LogFactory] to define the LogFactory subclass to use..."); } try { InputStream names1 = getResourceAsStream(contextClassLoader, "META-INF/services/org.apache.commons.logging.LogFactory"); if(names1 != null) { BufferedReader name; try { name = new BufferedReader(new InputStreamReader(names1, "UTF-8")); } catch (UnsupportedEncodingException var7) { name = new BufferedReader(new InputStreamReader(names1)); } value = name.readLine(); name.close(); if(value != null && !"".equals(value)) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + value + " as specified by file \'" + "META-INF/services/org.apache.commons.logging.LogFactory" + "\' which was present in the path of the context" + " classloader."); } factory = newFactory(value, baseClassLoader, contextClassLoader); } } else if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No resource file with name \'META-INF/services/org.apache.commons.logging.LogFactory\' found."); } } catch (Exception var8) { if(isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] A security exception occurred while trying to create an instance of the custom factory class: [" + var8.getMessage().trim() + "]. Trying alternative implementations..."); } } }
即使用SPI机制,而正好在"org.slf4j:jcl-over-slf4j"包下的"META-INF/services"目录下有"org.apache.commons.logging.LogFactory"这个文件,文件中配置使用"org.apache.commons.logging.impl.SLF4JLogFactory"这个具体实现类。
因而,最后得到的Factory实例也是"org.apache.commons.logging.impl.SLF4JLogFactory"(该类由"jcl-over-slf4j"提供),那么接下来的步骤就跟原因1)中所描述的一样了。注意在接下来的步骤中,"org.apache.commons.logging.Log"从"org.slf4j:jcl-over-slf4j"加载得到,而不是从"commons-logging:commons-logging"加载得到。
但是这种解决重复依赖冲突问题的机制不具有通用性(比如在Log4J转SLF4J中,重复依赖冲突问题并没有采用该机制解决),是个奇技淫巧,不推荐使用。