likes
comments
collection
share

深度探究依赖冲突 NoSuchMethodError 问题解决之道

作者站长头像
站长
· 阅读数 9

一、背景

  • 由于公司parent-pom.xml父模块做了一次整体升级,下游相关服务部署测试时报错,报错内容如下:
2023-10-26 14:09:24.555 ERROR [main] [tid:TID: N/A|req:|cip:|channel:] [o.s.b.SpringApplication:858] - Application run failed
java. lang. NoSuchMethodError: com.google.common.base.Splitter.splitToList(Ljava/lang/Charsequence:)Ljava/util/List;
at com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer.initialize(ApolloApplicationContextInitializer.java:101) ~[apollo-client-1.7.1
20210812.jar!/:1.7.1-20210812]
at com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer.postProcessEnvironment (ApolloApplicationContextInitializer,java:166) ~[apollo-
client-1.7.1-20210812.jarl/:1.7.1-20210812]
at org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent(ConfigFileApplicationListener.java:179) ~[spr
ing-boot-2.1.4. RELEASE. jarl/:2.1.4.RELEASE]
at org.springframework.boot.context. config.ConfigFileApplicationListener.onApplicationEvent(ConfigFileApplicationListener.java:165) ~[spring-boot-2.1.4.RELE
ASE.jarl/:2.1.4.RELEASE]
at org.springframework.context.event. SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) -[spring-context-5.1.6.R
ELEASE.jarl/:5.1.6. RELEASE]
at org.springframework.context.event. SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster,java:165) -[spring-context-5.1.6.REL
EASE. jarl/:5.1.6. RELEASE]
at org.springframework.context.event. SiDpleApplicationEventMulticaster.multicastEvent (SimpleApplicationEventMulticaster.java:139) ~[spring-context-5.1.6.REL
EASE.jar!/:5.1.6.RELEASE]
at org.springframework.context.event. SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127) -[spring context-5.1.6.REL
EASE, jarl/:5.1.6. RELEASE]
at org.springframework.boot.context.event.EventPublishingRuntistener.environmentPrepared(EventPublishingRuntistener. java:/5) ~[spring-boot-2.1.4.RELEASE. jar
!/:2.1.4.RELEASE]
at org.springframework.boot.SpringapplicationRunListeners.environmentPrepared(SpringApplicationkuntisteners.java:54) -[spring-boot-2.1.4.RELEASE.jar!/:2.1.4
RELEASE]
at org.springframework. boot. SpringApplication.prepareEnvironment (SpringApplication.java:347) -[spring-boot-2.1.4.RELEASE.jar!/:2.1.4.RELEASE]
at org. springframework. boot. SpringApplication.run(SpringApplication.java:306) -[spring-boot-2.1.4. RELEASE.jarl/:2.1.4.RELEASE]
at org.springframework. boot. SpringApplication. run(SpringApplication, java:1260) |[spring boot-2.1.4.RELEASE. jar!/:2.1.4. RELEASE]
at org.springframework.boot. SpringApplication.run(SpringApplication.java:1248) -[spring-boot-2.1.4.RELEASE.jar!/:2.1.4.RELEASE]
at tech.qifu. jinke.yushu.dds.app. YushuDdsAppApplication.main(YushuodsAppApplication.java:24) -[classes]/:7]
at sun. reflect. NativeMethodAccessorImpl. invoke0(Native Method) [ ?: 1.8.0 352)
at sun. reflect. NativeMethodAccessorImpl. invoke(NativeMethodAccessorImpl.java:62) [7:18.0 _352]
at sun. reflect. DelegatingMethodAccessorImpl. invoke(DelegatingMethodAccessorImol.java:43) -[ ?: 1.8.0 352]
at java. lang. reflect.Method. invoke(Method. java:498) ~[7:1.8.0 352]
at org.springframework.boot. loader.MainMethodRunner.run(MainMethodRunner. java:47) -[jsbank-yushu-dds_231026115655932-1b4030f.jar :? ]
at org. springframework.boot. loader.Launcher. launch(Launcher, java: 86) ~[isbank-yushu-dds 231026115655932-1b4030f. jar:7
at org.springframework. boot. loader.Launcher. launch(Launcher. java:50) ~[jsbank-yushu-dds 231026115655932-1b4030f.jar :? ]
at org.springframework.boot. loader. JarLauncher.main(JarLauncher. java:51) -[jsbank-yushu-dds_231026115655932-1b4030f.jar :? ]

二、问题定位

2.1. 第一次定位

日志最关键的一句如下:由于程序在运行时调用com.google.common.base.Splitter类的splitToList函数,但未找到该方法导致抛异常,于是着手在服务上寻找该类的依赖包

java.lang.NoSuchMethodError: com.google.common.base.Splitter.splitToList(Ljava/lang/Charsequence:)Ljava/util/List;
  • 在IDEA中通过全限定名寻找该类,结果如下:该类属于guava包

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 看到该类属于guava包,下意识认为是jar包冲突问题,毕竟guava包冲突是常见问题,于是着手分析应用的pom.xml依赖,如下:一片爆红

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 此时到服务器上找到应用,解压后查看依赖:BOOT-INF/lib目录下存在的是guava:32.0.1-jre.jar包 深度探究依赖冲突 NoSuchMethodError 问题解决之道
  • 此时感觉已经定位到问题了,认为只要将所有依赖guava:32.0.1-fre的包全都<exclusion>剥离,只保留应用自身的guava:20.0依赖应该可以解决,如下:此时maven依赖清爽了很多

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 随后打包部署验证,遗憾的是和之前的报错一样:java.lang.NoSuchMethodError: com.google.common.base.Splitter.splitToList(Ljava/lang/Charsequence:)Ljava/util/List;

2.2. 第二次定位

  • 经过了第一次失败后开始着手看栈日志,定位报错位置代码,如下:ApolloApplicationContextInitializer.java:101
2023-10-26 14:09:24.555 ERROR \[o.s.b.SpringApplication:858\] - Application run failed
 java.lang.NoSuchMethodError: com.google.common.base.Splitter.splitToList(Ljava/lang/Charsequence:)Ljava/util/List; at 
 com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer.initialize(ApolloApplicationContextInitializer.java:101) ~\[apollo-client-1.7.1]
  • 报错位置显示为:ApolloApplicationContextInitializer.java 类 101 行引发报错,于是翻看源码,如下:正是此处调用com.google.common.base.Splitter.splitToList函数导致报错

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 于是推断该apollo包的pom.xml中应该依赖了guava,只要我的应用在外部指定的guava版本和apollo依赖的guava版本保持一致应该就可以解决此问题,于是查看apollo:pom.xml依赖,如下:

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 此时又认为定位到了问题,认为只要将应用的guava版本改为19.0即可,如下:

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 于是打包部署验证,遗憾的是和之前的异常一样 = =!java.lang.NoSuchMethodError: com.google.common.base.Splitter.splitToList(Ljava/lang/Charsequence:)Ljava/util/List;

2.3. 第三次定位

  • 经过了前两次的失败,已经不在单纯的认为是guava版本冲突了,思考:NoSuchMethodError异常其实很好理解,即Splitter类中没有splitToList函数;于是翻看guava:32.0.1-jre/20.0/19.0 这三个不同版本包中的Splitter类,如下:三个版本的包皆有splitToList函数

深度探究依赖冲突 NoSuchMethodError 问题解决之道 深度探究依赖冲突 NoSuchMethodError 问题解决之道 深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 于是推测应该是其他依赖包中依赖了guava包,而在类加载过程中指向了错误的依赖包而没有依赖我们指定为的guava包从而导致这个问题,于是开始逐一搜索含有com.google.common.base.Splitter类的jar包,一番寻找后定位到了hive-exec:2.3.2.jar,如下:

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 进到hive-exec:2.3.2.jar包目录下发现Splitter类没有splitToList函数,如下图:

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 此时认为已经定位到了问题,解决方案是在应用中将hive-exex.jar依赖通过<exclusion>剥离guava包即可
  • 但是发现应用中的pom.xml中本就排除了guava依赖,如下:这就有点奇怪了

深度探究依赖冲突 NoSuchMethodError 问题解决之道

2.4. 第四次定位

  • 经过前几次的尝试,仍认为是hive-exec.jar包问题,于是又仔细查看了hive-exec的结构目录,果然有了新的发现,如下:hive-exec.jar代码包中就嵌入了com.google.common.base.Splitter类,从pom.xml层面根本无法通过<exclusion>剥离。

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 本质:原因是hive-exec通过Maven Shade Plugin的方式将所有依赖打成了shadow JAR,这里解释一下Maven Shade Plugin:

Maven Shade Plugin通常用于创建"fat JAR"(也称为"uber JAR")或"shadow JAR",这种 JAR 文件包含了所有应用程序的依赖项,以便在运行时可以独立运行。这种 JAR 文件通常不再包含BOOT-INF目录,而是将所有依赖项合并到了JAR文件的根目录下。

当使用Maven Shade Plugin创建"fat JAR"时,它会合并所有的依赖项,包括JAR文件和类文件,将它们放在一个JAR文件中。这个JAR文件没有BOOT-INF目录,而是将所有的类和资源放在一个扁平的结构中。这意味着应用程序的类和依赖项的类都在同一个类路径下,没有明确的区分。

  • 正是因为Maven Shade Plugin 将所有依赖放在同一个结构中,这才导致传统的 <exclusion>无法剥离guava依赖,因为该依赖已经嵌入到代码中!

解决方案

  • 经过了上面四次的尝试已经定位到了问题,此时有如下三种解决方案:
  • 直接剔除hive-exec依赖包
  • 升级hive-exec到3.x版本,3.x版本中Splitter类包含splitToList函数
  • 通过-Djava.class.path=./lib/guava-20.0.jar 启动参数来调整类加载顺序,优先加载指定的guava包
  • 通<classifier>core</classifier>配置指定特定版本的依赖项

第一种风险较大不推荐,第二种升级可能会存在潜在问题,第三种改动最小也最优雅,前三种实现方式都不复杂,但是这里想介绍一下第四种方式:下面将介绍<classifier>标签作用

classifier

标签通常用于 Maven 依赖项的配置,以指定要使用的依赖项的分类(classifier)。分类是一种方式,它允许您为同一依赖项的不同版本或变种(variant)创建不同的标识。

一般情况下,Maven 依赖项的坐标包括以下元素:

  • groupId:依赖项的组标识。
  • artifactId:依赖项的工件标识。
  • version:依赖项的版本号。

然而,有时候同一个 groupId、artifactId 和 version 的依赖项可能有多个变种或版本。这时, 元素可以用来指定使用哪个特定版本或变种的依赖项。

例如,假设有一个库 my-library 有两个不同的版本:1.0 和 1.0-core,您可以使用 元素来指定使用1.0-core版本,如下所示:

<dependency> 
	<groupId>com.example</groupId> 
	<artifactId>my-library</artifactId> 
	<version>1.0</version> 
	<classifier>core</classifier> 
</dependency>

在这个示例中, 元素指定了使用 1.0-core 版本的库,而不是默认的 1.0 版本。

分类通常用于区分不同构建或变种,以满足不同的需求。它允许您在同一个项目中同时使用不同版本的依赖项,而不必修改它们的工件标识或版本号。

实施

  • 此时我们查看maven仓库中的hive-exec:2.3.2所对应的不同依赖项,maven地址

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 所有变种依赖如下:而应用只需用到hive-exex-2.3.2-core.jar依赖即可满足使用;
  • 注意:如果你的应用还需要source等依赖则要按需依赖!

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 更改maven配置后,查看dependence依赖发现google目录已消失,如下:

深度探究依赖冲突 NoSuchMethodError 问题解决之道

  • 此时打包部署测试后问题解决

总结

此次问题的排查总结如下:

  1. 详细的日志和异常信息非常关键:仔细分析应用程序的异常信息和日志可以提供关键线索,帮助快速定位问题。
  2. 深入了解依赖关系:了解应用程序的依赖关系,特别是外部库和其版本,有助于更好地理解问题的根本原因。
  3. 查看应用程序的依赖项:检查应用程序的依赖项,尤其是可能与异常相关的依赖项,可以帮助确定问题来源。
  4. 逐步分析和排查:问题的定位通常需要逐步分析和排查。在解决问题时,不要害怕多次尝试,每次都获取更多信息。
转载自:https://juejin.cn/post/7350977538275852297
评论
请登录