共计 4316 个字符,预计需要花费 11 分钟才能阅读完成。
导读 | 今天碰到群里小伙伴问,线上程序好像有多个不同版本的 Netty 包,怎么去看到底加载了哪一个? |
在说如何看之前,先来说说,当你开始意识到项目里有多个不同版本的 Jar 包,都是因为遇到了这几个异常:
- 1、java.lang.NoSuchMethodException:自己代码中调用了某个方法,因为加载了其他版本的 jar,这个版本正好没这个方法。
- 2、java.lang.NoClassDefFoundError:编译时候是好的,但是运行的时候,因为加载的 jar 版本问题,没有这个类。
- 3、java.lang.ClassNotFoundException:在动态加载某个 Class 的时候,因为要加载的 jar 不是正确的版本,而导致找不到这个类。
当你在本地运行 ok,但到服务器上发现出现这些错误的时候,就要意识到很可能是 jar 冲突了 (有相同依赖存在多个版本)。这个问题往往也会有这样的表现:多实例部署的时候,有的实例是好的,有的实例则不行。
根据之前分析的异常种类,我们可以去运行中的现场确认当前加载的问题。
这里我们可以使用阿里开源的 Arthas 工具,如果第一次用,那么按下面操作先安装再运行:
curl -O https://arthas.aliyun.com/arthas-boot.jar | |
java -jar arthas-boot.jar |
运行好之后,会打印出当前运行着的 java 应用,比如:
[3.4.6 | ] arthas-boot version:|
[40611 already using port 3658 | ] Process|
[40611 already using port 8563 | ] Process|
[and input the serial number of the process, eg : 1. Then hit ENTER. | ] Found existing java process, please choose one|
* [1]: 40611 chapter4-3-0.0.1-SNAPSHOT.jar | |
[37786 | ]:
通过输入编号选择要查看的 java 应用,比如这里选择:1,进入到 chapter4-3-0.0.1-SNAPSHOT.jar 中去。
下面介绍两个重要命令:
第一个:sc 命令,我们确认一下可能冲突的 jar 包下面,是否有对应的 class。有些不同版本包下 class 就不一样,马上就可以分辨出来。
比如,通过下面的命令,我们查看一下 com.didispace 包下有什么类:
[arthas@40611]$ sc com.didispace.* | |
com.didispace.chapter43.Chapter43Application | |
com.didispace.chapter43.Chapter43Application$$EnhancerBySpringCGLIB$$8b82b194 | |
com.didispace.chapter43.UploadController | |
Affect(row-cnt:3) cost in 6 ms. |
第二个:sm 命令,查看具体某个类有哪些方法。有的版本差异就是去掉了某个方法,这个时候我们就可以通过这个命令来查看。
比如,通过下面的命令,我们查看一下 com.didispace.chapter43.UploadController 类下有些什么方法:
[ | ]$ sm com.didispace.chapter43.UploadController|
com.didispace.chapter43.UploadController ()V | |
com.didispace.chapter43.UploadController create(Lorg/springframework/web/multipart/MultipartFile;)Ljava/lang/String; | |
com.didispace.chapter43.UploadController uploadPage()Ljava/lang/String; | |
Affect(row-cnt:3) cost in 5 ms. |
在确认完是加载错误的情况下,我们要去解决冲突。那么解决冲突要做的就是找到到底哪里冲突了以及我们要去除或者强制
找出版本冲突的方法:使用 Maven 命令:mvn -U dependency:tree -Dverbose。
命令执行之后,会在控制台以树状形式列出所有依赖内容,然后通过搜索的方式查找冲突的包,看看都是从哪个依赖中带进来的 (在 IDEA 中搜索会高亮,更容易找到)。
[-3:jar:0.0.1-SNAPSHOT | ] com.didispace:chapter4|
[2.4.1:compile | ] +- org.springframework.boot:spring-boot-starter-web:jar:|
[2.4.1:compile | ] | +- org.springframework.boot:spring-boot-starter:jar:|
[2.4.1:compile | ] | | +- org.springframework.boot:spring-boot:jar:|
[2.4.1:compile | ] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:|
[2.4.1:compile | ] | | +- org.springframework.boot:spring-boot-starter-logging:jar:|
[1.2.3:compile | ] | | | +- ch.qos.logback:logback-classic:jar:|
[1.2.3:compile | ] | | | | \- ch.qos.logback:logback-core:jar:|
[2.13.3:compile | ] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:|
[2.13.3:compile | ] | | | | \- org.apache.logging.log4j:log4j-api:jar:|
[1.7.30:compile | ] | | | \- org.slf4j:jul-to-slf4j:jar:|
[1.3.5:compile | ] | | +- jakarta.annotation:jakarta.annotation-api:jar:|
[1.27:compile | ] | | \- org.yaml:snakeyaml:jar:|
[2.4.1:compile | ] | +- org.springframework.boot:spring-boot-starter-json:jar:|
[2.11.3:compile | ] | | +- com.fasterxml.jackson.core:jackson-databind:jar:|
[2.11.3:compile | ] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:|
[2.11.3:compile | ] | | | \- com.fasterxml.jackson.core:jackson-core:jar:|
[2.11.3:compile | ] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:|
[2.11.3:compile | ] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:|
[2.11.3:compile | ] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:|
[2.4.1:compile | ] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:|
[9.0.41:compile | ] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:|
[3.0.3:compile | ] | | +- org.glassfish:jakarta.el:jar:|
[9.0.41:compile | ] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:|
[5.3.2:compile | ] | +- org.springframework:spring-web:jar:|
[5.3.2:compile | ] | | \- org.springframework:spring-beans:jar:|
[5.3.2:compile | ] | \- org.springframework:spring-webmvc:jar:|
[5.3.2:compile | ] | +- org.springframework:spring-aop:jar:|
[5.3.2:compile | ] | +- org.springframework:spring-context:jar:|
[5.3.2:compile | ] | \- org.springframework:spring-expression:jar:|
[2.4.1:compile | ] +- org.springframework.boot:spring-boot-starter-thymeleaf:jar:|
[3.0.11.RELEASE:compile | ] | +- org.thymeleaf:thymeleaf-spring5:jar:|
[3.0.11.RELEASE:compile | ] | | +- org.thymeleaf:thymeleaf:jar:|
[2.0.5.RELEASE:compile | ] | | | +- org.attoparser:attoparser:jar:
解决版本冲突的方式主要两种:
- 1、通过上面的命令找到不需要的版本之后,在引入的依赖中,使用 exclusions 将其排除
- 2、在 pom.xml 中强制指定要使用的版本,这样这个优先级最高,就不会引入其他版本要带进来的版本了。
