8

spring-boot项目,启用log4j2后,报以下错误:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/panjie/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/panjie/.m2/repository/ch/qos/logback/logback-classic/1.1.11/logback-classic-1.1.11.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]

原因:一个接口,被两个实现类实现了。然后,程序在启动获取时,只想获取一个。
这个接口是:org.apache.logging.slf4j.Log4jLoggerFactory
两个实现类分别是:
ch/qos/logback/logback-classic/1.1.11/logback-classic-1.1.11.jar!/org/slf4j/impl/StaticLoggerBinderorg/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class

可以在idea中使用ctrl + o, 然后输入StaticLoggerBinder,即可定位到两个实现类。然后在实现类中,按alt + f1 再回车,便可以定位到引用的包。

定位两个包如下:
apache
clipboard.png

ch.qos
clipboard.png

我们发现两个实现类,都是maven替我们引入的,所以需要在maven的配置文件上下手,排除一个。

步骤

  1. 看提示
  2. 找到该实现类所在的包,是pom.xml中,哪行依赖引入的。
  3. 在该依赖中,排除该包。

看提示

http://www.slf4j.org/codes.html#multiple_bindings,应该是这个问题发生的频率比较高,所以官网上专门对其进行了说明。不过官方在讲,某一个包中引用了多个,然后加入排除,与我们当前面临的情况不同。

找依赖

打开pom.xml,在文件内容上,右键,选择 Diagrams -> show dependencies...ctrl + f 输入logback-classic

clipboard.png

按图的关系,我们发现,原来是由pdftest引入的。

你也可以直接双击pdftest或是logback-classic这两个小方框,来找到其实对应引入的语句。

排除

pom.xml中找到pdftest, 并添加排除

 <!--itextpdf test-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>pdftest</artifactId>
            <version>${itextpdf.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

测试

启动单元测试
以前的错误消息了,但新的错误如下:

java.lang.NoClassDefFoundError: ch/qos/logback/classic/turbo/TurboFilter

说明,方向可能错误,恢复代码。继续猜原因。



这个原因我猜应该是这样:
虽然我们可以这样排除掉这个logback-classic中的实现类,但是:pdftest在运行时,却指定了需要logback-classic中的实现类,所以就报错了。

而前面之所以报找到了多个multiple的错误,由于报错的代码所在的包:

  1. 自带了一个实现类。
  2. 初始化时,扫描了所有的实现类,然后了多个(自带了一个,以前我们的logback-classic还有一个),所以报错。

按照这个思想,我们换一种解法;

换思路

那么,我们换一种解法:

  1. 找到报错的类。
  2. 看报错类的包的名称。
  3. 看这个包是谁引进来的。
  4. 在这个包中,排除掉其自带的实现类。

clipboard.png

clipboard.png

clipboard.png

再次启动,错误消失。

总结

SLF4JLogFactory在初始化时,会扫描所有的实现类,所以发现有多个,就会报错。我们在以入其它的包时,不巧的是,引入了一个实现相同接口Log4jLoggerFactory的实现类。加上sfl4j本身又自己带了一个过来,结果就有两个了,所以出错了。
最后的解决方案就是去一个,去哪个呢?通过实验,我们发现去除sfl4j自带的,不会出现问题。如果去除另一个,由于另一个的类,直接被其它类实例化了,去除了则会报错。

收获:

  1. 学习了如何通过IDEA查找包之间的依赖,以及按需求排除依赖。
  2. 学到了一种思想:在开放的系统中,对某个接口的实现类进行初始化时,可以先获取所有实现了某个接口的实现类。如果该实现类唯一,则直接实例化。如果不唯一,则可以读取配置文件,按配置文件的配置进行实例化。从而达到了:定义接口 -> 给出实现类 -> 其它用户按接口规范自定义实现类 -> 配置实现类 -> 调用用户自定义实现类的目的。

潘杰
3.1k 声望239 粉丝