解决引入第三方SDK导致的依赖冲突问题
出现问题:
原因:导入第三方sdk
解决思路:刷新maven,清理,排除依赖
在排除掉可能的依赖后发现还是不行,分析依赖关系,打开idea自带的maven树查看工具:
通过maven依赖图谱我们发现,所有的日志依赖都来源于spring-boot-starter,这也是Springboot的祖宗依赖,并且,该sdk是一整个完整的SpringBoot工程,而不是常见的模块式。
直接排他祖宗:
问题解决,分析出现原因:
根本原因:jvm的类加载机制
问题产生过程:两个项目的依赖版本不同,假设我们项目是:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
引入的第三方sdk中的依赖是:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
Spring Boot 启动时,类加载器的行为如下:
步骤 1:类加载器扫描 classpath
Spring Boot 启动时,ApplicationClassLoader
负责扫描 classpath 下的 JAR 文件,发现:
-
logback-classic-1.2.10.jar
(来自我们的项目依赖) -
logback-classic-1.2.3.jar
(来自 SDK)
Java 规定 同一个类(全限定名相同),只能被一个类加载器加载一次,否则会导致冲突。
步骤 2:类加载器加载 SLF4J
SLF4J 主要涉及以下类:
org.slf4j.LoggerFactory
(SLF4J 的核心类)
org.slf4j.impl.StaticLoggerBinder
(绑定具体日志实现)
Spring Boot 在初始化日志时,会执行:
Logger logger = LoggerFactory.getLogger(MyApp.class);
这时,类加载器会加载 org.slf4j.impl.StaticLoggerBinder
类,而这个类分别存在于:
-
logback-classic-1.2.10.jar
-
logback-classic-1.2.3.jar
假设 ApplicationClassLoader
先加载 logback-classic-1.2.3.jar
,这时 JVM 缓存了老版本,但你的 logback-classic-1.2.10.jar
中的方法签名可能发生了变化。
3. 发生 AbstractMethodError
的原因
由于 logback-classic-1.2.3.jar
是 较早的版本,它可能没有某些方法,比如:
// 1.2.3 版本的方法
public boolean supportsSourceType(Class<?> sourceType) { return true; }
// 1.2.10 版本方法(升级后可能有不同签名)
public boolean supportsSourceType(Class<?> sourceType, boolean flag) { return true; }
然后 Spring Boot 依赖的是 logback-classic-1.2.10.jar
版本的方法:
listenerAdapter.supportsSourceType(someClass);
但由于 logback-classic-1.2.3.jar
已经被类加载器加载,JVM 仍然调用的是老版本的方法,而 logback-classic-1.2.3.jar
里没有这个方法,就会发生 AbstractMethodError
。
Exception in thread "main" java.lang.AbstractMethodError at org.springframework.context.event.GenericApplicationListenerAdapter.supportsSourceType
总结:
-
类加载器只能加载一个类的一个版本,如果
logback-classic-1.2.3.jar
先被加载,Spring Boot 调用新版本的方法就会失败。 -
方法签名变化导致
AbstractMethodError
,因为类加载器仍然引用的是旧的logback-classic-1.2.3.jar
版本,而新代码需要1.2.10
的方法。 -
该SDK是完整的SpringBoot工程,而不是模块,模块化 SDK 可以通过适当的 类加载器管理、依赖管理、版本对齐 等方式,确保它的日志框架不会影响到 Spring Boot 项目的日志实现