SLF4J: Class path contains multiple SLF4J bindings.
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/StaticLoggerBinder
与org/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
ch.qos
我们发现两个实现类,都是maven
替我们引入的,所以需要在maven
的配置文件上下手,排除一个。
步骤
- 看提示
- 找到该实现类所在的包,是
pom.xml
中,哪行依赖引入的。 - 在该依赖中,排除该包。
看提示
http://www.slf4j.org/codes.html#multiple_bindings,应该是这个问题发生的频率比较高,所以官网上专门对其进行了说明。不过官方在讲,某一个包中引用了多个,然后加入排除,与我们当前面临的情况不同。
找依赖
打开pom.xml
,在文件内容上,右键,选择 Diagrams
-> show dependencies...
,ctrl + f
输入logback-classic
按图的关系,我们发现,原来是由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
的错误,由于报错的代码所在的包:
- 自带了一个实现类。
- 初始化时,扫描了所有的实现类,然后了多个(自带了一个,以前我们的
logback-classic
还有一个),所以报错。
按照这个思想,我们换一种解法;
换思路
那么,我们换一种解法:
- 找到报错的类。
- 看报错类的包的名称。
- 看这个包是谁引进来的。
- 在这个包中,排除掉其自带的实现类。
再次启动,错误消失。
总结
SLF4JLogFactory
在初始化时,会扫描所有的实现类,所以发现有多个,就会报错。我们在以入其它的包时,不巧的是,引入了一个实现相同接口Log4jLoggerFactory
的实现类。加上sfl4j
本身又自己带了一个过来,结果就有两个了,所以出错了。
最后的解决方案就是去一个,去哪个呢?通过实验,我们发现去除sfl4j
自带的,不会出现问题。如果去除另一个,由于另一个的类,直接被其它类实例化了,去除了则会报错。
收获:
- 学习了如何通过IDEA查找包之间的依赖,以及按需求排除依赖。
- 学到了一种思想:在开放的系统中,对某个接口的实现类进行初始化时,可以先获取所有实现了某个接口的实现类。如果该实现类唯一,则直接实例化。如果不唯一,则可以读取配置文件,按配置文件的配置进行实例化。从而达到了:定义接口 -> 给出实现类 -> 其它用户按接口规范自定义实现类 -> 配置实现类 -> 调用用户自定义实现类的目的。