Spring Boot混合类加载引发java.lang.NoSuchMethodError问题咨询
1. NoSuchMethodError的本质:类加载导致的类型不兼容
虽然两个版本的EJML都实现了subtract(DMatrixD1,DMatrixD1,DMatrixD1)方法,但JVM判断类的唯一性是全限定类名 + 类加载器。你的tsne库(0.39)里的DMatrixD1和LDA库(0.41)里的DMatrixD1,即使全限定名完全相同,也是两个独立的类实例——因为它们来自不同的JAR包,被类加载器加载为互不关联的类。
当代码从tsne库(0.39)发起调用,传入的是0.39版本的DMatrixD1对象,但此时JVM已经优先加载了LDA库(0.41)里的CommonOps_DDRM类。这个类的subtract方法期望接收的是0.41版本的DMatrixD1参数,而你传入的0.39版本对象在JVM看来是完全不同的类型,因此会判定“找不到匹配的方法签名”,抛出NoSuchMethodError。
2. 类加载顺序是核心诱因
你通过-verbose:class发现CommonOps_DDRM来自0.41版本的LDA库,这正是问题根源:Spring Boot的类加载器会根据JAR的加载顺序优先加载LDA库中的EJML类,而非tsne库的。后续tsne库的代码调用这个已加载的高版本类方法,就会出现类型不兼容的冲突。
1. 最彻底方案:停止打包胖JAR,统一依赖版本
自研的tsne和LDA库不应该将EJML打包为胖JAR的一部分,改为通过依赖管理引入EJML,让Spring Boot项目统一控制版本:
- 对于Maven项目:修改tsne和LDA的
pom.xml,移除shade插件中打包org.ejml包的配置,确保EJML依赖为compile范围(不打包到JAR内)。 - 对于Gradle项目:修改
build.gradle,在shadowJar任务中排除org.ejml包,或直接使用普通JAR打包。 - 在Spring Boot项目的依赖管理块中强制指定EJML版本为0.39(或0.41,需验证所有库兼容),示例Maven配置:
<dependencyManagement> <dependencies> <dependency> <groupId>org.ejml</groupId> <artifactId>ejml-core</artifactId> <version>0.39</version> </dependency> <!-- 其他EJML模块如ejml-ddrm等同理配置 --> </dependencies> </dependencyManagement>
2. 临时方案:调整类加载顺序
如果暂时无法修改自研库,可以调整Spring Boot的类加载顺序,让tsne库的EJML类优先加载:
- 修改JVM启动参数,将tsne的JAR放在类路径的最前面(但这种方式不稳定,容易受打包顺序影响)。
3. 复杂场景方案:类加载器隔离
如果必须保留胖JAR结构,可以使用Spring Boot的自定义类加载器特性,将tsne和LDA库分别放在不同的类加载器中,避免类冲突。比如利用spring-boot-maven-plugin的layers功能,或自定义ClassLoader实现,但这需要较多的配置和测试。
内容的提问来源于stack exchange,提问作者lejon

