spring集成SLF4J时的问题及延展

Posted 小杨Vita

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring集成SLF4J时的问题及延展相关的知识,希望对你有一定的参考价值。

Spring Framework 所使用的日志接口一直都是 commons-logging,Apache Commons Logging是一个通用的日志接口,与slf4j简单日志门面类似。如果它搜索到应用添加了log4j的引用,那么将直接使用log4j,如果你想用现在越来越流行的SLF4J来接管日志接口,则需要使用SLF4J提供的 jcl-over-slf4j 把 commons-logging API 转接到 SLF4J API 上。

问题

一般的教程在介绍maven配置时除了让添加 slf4j-api 和 jcl-over-slf4j(不可缺少) 的jar包外,还会提醒移除spring中对 commons-logging 的依赖(如下)。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>$spring-version</version>
    <scope>runtime</scope>
    <exclusions>
    <exclusion>
        <artifactId>commons-logging</artifactId>
        <groupId>commons-logging</groupId>
    </exclusion>
    </exclusions>
</dependency>

注意到jcl-over-slf4j几乎复制了commons-logging的所有方法、包路径,因此移除了commons-logging自然就全由jcl-over-slf4j接管。然而诸位有没发现就算你不移除commons-logging依赖,日志依然是由jcl-over-slf4j接管的,因此依然可以识别到SLF4J,这到底是如何做到的呢?

分析

Apache Commons Logging (原名 Jakarta Commons Logging,JCL) 只提供 log 接口,具体的实现则在运行时动态寻找,如果找不到外部实现那么将使用自带的实现类。

我们先从调用开始说起,JCL的调用如下:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log log = LogFactory.getLog(Main.class.getName());

getLog()方法的实现如下:

 public static Log getLog(Class clazz) 
     return (getFactory().getInstance(clazz));
 

也就是说JCL先通过getFactory()搜索工厂实现类,然后使用它来获取Log实例。getFactory()方法很长,我大致说下加载的过程(markdown自动生成的流程图有点问题):

Created with Raphaël 2.1.2 开始寻找Factory 找不到 Factory缓存 找不到环境变量org.apache.commons. logging.LogFactory设置的类 找不到日志实现服务(service) 找不到commons-logging.properties 文件中org.apache.commons .logging.LogFactory设置的类 使用自带的LogFactoryImpl类 缓存Factory 寻找完成 返回null yes no yes no yes no yes no yes no

通常环境变量和配置文件都是空的,关键步骤在于第三步中的“找到日志实现服务”。JDK中关于该步骤的注解如下:

尝试使用JDK1.3定义的服务加载机制来搜寻服务,它需要读取META-INF/services目录下的文件,该文件的内容指定了实现所需接口的类。

这其实就是Java SPI(Service Provider Interface)的约定。一个服务通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现。按照SPI制定的标准提供方需要在资源路径META-INF/services目录下放置一个文本文件,文件的命名为该服务接口的全限定名,内容为实现类的全限定名。

好,这时候我们打开jcl-over-slf4j.jar文件,可以看到在META-INF/services目录下有一个org.apache.commons.logging.LogFactory文件,内容为org.apache.commons.logging.impl.SLF4JLogFactory,该实现类就在本jar包类。通过这种SPI约定的加载方式,SLF4J就接管了Apache Commons Logging,然后只需要引入SLF4J针对log4j或logback提供的桥接包,就可以让spring表面上使用的是Apache Commons Logging,实际上用的却是你提供的日志框架。

延展

如果你不想接入SLF4J,那么最后使用的自带LogFactoryImpl类将会依次按照以下顺序查找,若找到则直接加载该实现类(实现类也是自带的commons-logging.jar中):

  1. org.apache.commons.logging.impl.Log4JLogger
  2. org.apache.commons.logging.impl.Jdk14Logger
  3. org.apache.commons.logging.impl.Jdk13LumberjackLogger
  4. org.apache.commons.logging.impl.SimpleLog

可以看到如果你引入了log4j的jar包,优先也将使用log4j,那么为什么要用SLF4代替Commons Logging呢?大致原因有:

  1. SLF4J是编译时绑定到具体的日志框架,性能优于采用运行时搜寻的方式的commons-logging。但其代价便是如果你需要使用某一种日志实现,那么你必须手动引入该日志实现的SLF4J桥接包(见本文末尾)。
  2. SLF4J提供了更好的日志记录方式,带来下这几方面的好处:
    1. 更好的可读性;
    2. 不需要使用logger.isDebugEnabled()来解决日志因为字符拼接产生的性能问题。
  3. Apache Common Logging使用的类加载机制无法在OSGI环境下工作。

SLF4和Commons Logging其实都是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。也就是说上述的方法其实就是双层门面,一层套一层。

此外,在不用SLF4J时,Apache Common Logging默认的Log4JLogger使用的1.x版本的Log4J,若要使用2.x版本需要加入log4j提供的Apache Commons Logging 桥接器log4j-jcl.jar,其原理也是在META-INF/services目录下配置了自己的实现类org.apache.logging.log4j.jcl.LogFactoryImpl

最后附上一张SLF4J提供的所有桥接方案,开发者须针对引入的日志实现来选择对应的桥接包。

以上是关于spring集成SLF4J时的问题及延展的主要内容,如果未能解决你的问题,请参考以下文章

Spring集成Log4j日志框架

SpringBoot使用日志

日志框架之日志门面SLF4J的使用

日志门面框架Slf4j

java日志统一集成的两种方案

java日志统一集成的两种方案