Java日志框架——SLF4J

一、基本概念
SLF4J是一个日志框架抽象层,底下绑定具体的日志框架,比如说Log4J,Logback,Java Logging API等。SLF4J也有自身的默认实现,但是我们还是主要以日志框架抽象层的身份使用SLF4J。

要使用SLF4J,得包含对"org.slf4j:slf4j-api"的依赖。


二、两个简单的例子

2.1、具体日志框架是默认实现
1)项目pom.xml的配置片段
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.12</version>
</dependency>
2)Java代码片段
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Main.class);
        logger.error("Hello World");
        System.out.println(logger.getClass());
    }
}

3)运行结果

如图1

                                                                                 图1

Java日志框架——SLF4J_第1张图片
4)分析
绑定的具体日志框架是默认实现,即是"SLF4J+NOP"方案,logger指向的是"org.slf4j.helpers.NOPLogger"类的实例,该类中的日志操作方法不做任何动作(即NOP)
"org.slf4j.helpers.NOPLogger"类在"org.slf4j:slf4j-api:1.7.12"中


2.2、具体日志框架是slf4j-simple
1)项目pom.xml的配置片段
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.12</version>
</dependency>
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.12</version>
</dependency>
2)Java代码片段
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Main.class);
        logger.error("Hello World");
        System.out.println(logger.getClass());
    }
}

3)运行结果

如图2

                                              图2


4)分析
绑定的具体日志框架是简单实现,即"SLF4J+Simple"方案,logger指向的是"org.slf4j.impl.SimpleLogger"类的实例,该类中的日志操作方法能够完成简单的正常的日志操作
"org.slf4j.impl.SimpleLogger"类在"org.slf4j:slf4j-simple:1.7.12"中


三、SLF4J绑定具体日志框架
SLF4J绑定具体日志框架有两种方案,分别是:不使用适配器类和使用适配器类。
以下面的代码为例进行说明:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Main.class);
        logger.error("Hello World");
        System.out.println(logger.getClass());
    }
}
在不使用适配器类方案中,logger指向的类实例A独立实现日志操作功能;在使用适配器类方案中,logger指向的类实例A中包含有另外的日志框架中的日志记录类实例B,A的日志操作功能的实现是通过委托给B来实现的。
3.1、不使用适配器类举例
在"SLF4J+Logback"方案中,项目的pom.xml中有如下片段:
<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>
以上代码中的logger指向的类实例A为"ch.qos.logback.classic.Logger"的实例对象,A独立实现日志操作功能


"SLF4J+Simple"方案和"SLF4J+NOP"方案都是以上这种类型。


3.2、使用适配器类举例
在"SLF4J+SLF4J-Log4J+Log4J"方案中,项目的pom.xml有以下片段:
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.12</version>
</dependency>
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
</dependency>
<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
</dependency>
以上代码中的logger指向的类实例A为"org.slf4j.impl.Log4jLoggerAdapter"的实例对象,A中包含一个"org.apache.log4j.Logger"类实例B,A的日志操作委托给B来实现

在"SLF4J+SLF4J-JCL+JCL+Log4J"方案中
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.12</version>
</dependency>
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jcl</artifactId>
        <version>1.7.12</version>
</dependency>
<!--Apache Commons Logging就是JCL-->
<dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1</version>
</dependency>
<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
</dependency>
而根据 《Java日志框架——JCL》在"SLF4J+SLF4J-JCL+JCL+Log4J"方案中,以上代码中的logger指向的类实例A为"org.slf4j.impl.JCLLoggerAdapter"的实例对象,A中包含一个"org.apache.commons.logging.impl.Log4JLogger"类实例B,实例B中包含一个"org.apache.log4j.Logger“类实例C,A的日志操作委托给B来实现,B又委托给C来实现


”SLF4J+SLF4J-Jdk14+Java Logging API“方案也是以上这种类型


3.3、绑定关系图示

如图3

                                                                                                                                      图3




四、声明依赖
在声明依赖的时候,根据图3,我们应该对所有相关的依赖都进行声明。
比如在"SLF4J+SLF4J-Log4J+Log4J"方案中,项目的pom.xml有以下片段用于声明依赖:
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.12</version>
</dependency>
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
</dependency>
<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
</dependency>
通过查看"org.slf4j:slf4j-log4j12:1.7.12"的pom.xml,可以发现其中有如下片段 
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
</dependency>
也就是说"org.slf4j:slf4j-log4j12:1.7.12"中含有对"org.slf4j:slf4j-api"和"log4j:log4j"的依赖,其实想想也是,在"org.slf4j:slf4j-log4j12:1.7.12"中的"org.slf4j.impl.Log4jLoggerAdapter"类既要继承实现"org.slf4j:slf4j-api"中的"org.slf4j.Logger"类,也要包含"log4j:log4j"中的"org.apache.log4j.Logger"类实例作为完成实际日志操作功能的对象,因而"org.slf4j:slf4j-log4j12:1.7.12"自然得包含对这两个包的依赖
通过以上这点,想要使用"SLF4J+SLF4J-Log4J+Log4J"方案,只需在项目pom.xml中声明如下依赖即可:
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
</dependency>
 
 

但是为了更好的可读性和更高的掌控度,还是使用刚开始的完整的声明方案比较好。

五、SLF4J绑定具体的日志框架的原理 

根据"2.2、具体日志框架是slf4j-simple"中的简单例子来进行说明。

执行"Logger logger = LoggerFactory.getLogger(Main.class);"语句的时候发生了什么?

1)查看"org.slf4j.LoggerFactory"的源代码可以发现,其中有如下这些方法

public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        if(DETECT_LOGGER_NAME_MISMATCH) {
            Class autoComputedCallingClass = Util.getCallingClass();
            if(nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", new Object[]{logger.getName(), autoComputedCallingClass.getName()}));
                Util.report("See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
            }
        }
        return logger;
 }
 public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
 }
public static ILoggerFactory getILoggerFactory() {
        if(INITIALIZATION_STATE == 0) {
            INITIALIZATION_STATE = 1;
            performInitialization();
         }

        switch(INITIALIZATION_STATE) {
        case 1:
            return TEMP_FACTORY;
        case 2:
            throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case 3:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("Unreachable code");
        }
}
private static final void performInitialization() {
        bind();
        if(INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }
}
private static final void bind() {
        String msg;
        try {
            Set e = findPossibleStaticLoggerBinderPathSet();
            reportMultipleBindingAmbiguity(e);
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = 3;
            reportActualBinding(e);
            fixSubstitutedLoggers();
        } catch (NoClassDefFoundError var2) {
            msg = var2.getMessage();
            if(!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                failedBinding(var2);
                throw var2;
            }


            INITIALIZATION_STATE = 4;
            Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
        } catch (NoSuchMethodError var3) {
            msg = var3.getMessage();
            if(msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
                INITIALIZATION_STATE = 2;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw var3;
        } catch (Exception var4) {
            failedBinding(var4);
            throw new IllegalStateException("Unexpected initialization failure", var4);
        }
}

依次调用执行的顺序为

Logger getLogger(Class<?> clazz)()
Logger getLogger(String name)()
ILoggerFactory getILoggerFactory()
void performInitialization()
void bind()
2)在bind()方法中,会去加载"org/slf4j/impl/StaticLoggerBinder.class",这个类由具体的日志框架提供
3)如果加载不到"org/slf4j/impl/StaticLoggerBinder.class"类,那么"getILoggerFactory()"方法会返回NOPLoggerFactory类,最后在"Logger getLogger(String name)"方法中返回NOPLogger类实例
4)如果加载到"org/slf4j/impl/StaticLoggerBinder.class"类,那么"getILoggerFactory()"方法会返回具体日志框架的日志工厂类,在本例中是"SimpleLoggerFactory"类,最后在"Logger getLogger(String name)"方法中返回SimpleLogger类实例


六、其他
1、在[1]中说到具体日志框架的绑定时机是"at comile time",其实从上节的内容知道,绑定是运行时绑定的,因而应该这样去理解"at compile time",在编译的时候,pom.xml中的依赖都已经配置好了,那么在编译的时候,从最终结果角度来看,绑定其实已经完成了
2、假如类路径中有多个具体日志框架提供的"org/slf4j/impl/StaticLoggerBinder.class"类,也就是"multibinding",SLF4J的处理机制是使用第一个找到的"org/slf4j/impl/StaticLoggerBinder.class"类
3、假如类路径中不存在"org/slf4j/impl/StaticLoggerBinder.class"类,SLF4J的处理机制不是抛出"类找不到"异常,而是使用NOP作为具体日志框架

4、在图03中的"native implementaion"和"non-native implementation"不是以”“是否以非Java语言实现”为区分标准,而是以“是否直接继承'org.slf4j.Logger'类”为区分标准

5、现在流行的日志框架解决方案是:SLF4J+Logback



参考文献:
[1]http://www.slf4j.org/manual.html

你可能感兴趣的:(Java日志框架——SLF4J)