一、布景

  • 由于公司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. 逐渐分析和排查:问题的定位一般需要逐渐分析和排查。在处理问题时,不要害怕屡次测验,每次都获取更多信息。