Java日志框架——JCL(Log4J,Java Logging API)转SLF4J过程中重复依赖冲突问题

如文章 《Java日志框架——JCL(Log4J,Java Logging API)转SLF4J》所述,在完成”JCL(Log4J,Java Logging API)转SLF4J“的过程中,要注意重复依赖冲突问题。
比如一个项目原本使用JCL日志框架(可以是具有对"commons-logging:commons-logging"或者"commons-logging:commons-logging-api"的依赖),现在要改用SLF4J日志框架,而且不能改变源代码,那么只能通过"jcl-over-slf4j"这个日志框架桥接器来实现。要想转换成功,项目中使用到的JCL相关类就应该由"jcl-over-slf4j"提供,此时如果不只配置了对"jcl-over-slf4j"的依赖,还配置了对"commons-logging:commons-logging"或者"commons-logging:commons-logging-api"的依赖,就会导致JCL相关类有可能由"commons-logging:commons-logging"或者"commons-logging:commons-logging-api"提供,这样子转换就不能成功。

转换有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“。


下面分别验证前两种转换中的重复依赖冲突问题。
一、Log4J到SLF4J转换
1、pom.xml
同时含有对"log4j:log4j"和"org.slf4j:log4j-over-slf4j"的依赖,pom.xml内容如下:
<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>

3、Java代码
import org.apache.log4j.Logger;

public class Main {
    public static void main(String[] args) {
        Logger logger = Logger.getLogger(Main.class);
        logger.error("hello world");
    }
}

4、运行结果

如图1。

                                                                                        图1

Java日志框架——JCL(Log4J,Java Logging API)转SLF4J过程中重复依赖冲突问题_第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
同时含有对"commons-logging:commons-logging"和"org.slf4j:jcl-over-slf4j"的依赖,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>

2、日志框架配置文件
不配置"commons-logging.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>  

3、Java代码
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

Java日志框架——JCL(Log4J,Java Logging API)转SLF4J过程中重复依赖冲突问题_第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中,重复依赖冲突问题并没有采用该机制解决),是个奇技淫巧,不推荐使用。


你可能感兴趣的:(Java日志框架——JCL(Log4J,Java Logging API)转SLF4J过程中重复依赖冲突问题)