AI摘要
(一) 从一次诡异的NoSuchMethodError说起
周五晚上正准备上线一个新功能,测试环境一切正常。但上线后不久,监控系统报警:某个核心服务频繁抛出java.lang.NoSuchMethodError: com.google.common.collect.Lists.partition。
排查过程:
- 本地开发环境正常,测试环境正常,只有生产环境异常
- 查看错误堆栈,发现是在调用一个工具类时发生的
- 使用
arthas在线诊断工具attach到生产环境JVM:
# 查看类加载的来源
sc -d com.google.common.collect.Lists- 发现生产环境加载的Guava版本是19.0,而本地是31.1-jre
根本原因: 某个间接依赖引入了低版本的Guava,由于Maven依赖调解的"最近路径原则",低版本覆盖了高版本。
(二) Maven依赖机制深度解析
1. 依赖传递原理
当项目A依赖B,B依赖C时,A会自动依赖C,这就是依赖传递。
项目A → 组件B(v1.0) → 组件C(v1.0)
↘ 组件D(v2.0) → 组件C(v2.0) [冲突!]2. 依赖调解原则
- 最短路径原则:路径短的依赖优先
- 最先声明原则:路径长度相同时,POM中先声明的优先
3. 依赖范围(Scope)详解
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope> <!-- 只对测试代码有效 -->
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope> <!-- 容器会提供,编译需要但打包排除 -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>
<!-- 默认compile范围,会传递 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope> <!-- 编译不需要,运行需要 -->
</dependency>(三) 依赖冲突排查实战
1. 使用dependency:tree分析依赖树
# 查看完整的依赖树
mvn dependency:tree
# 只查看与guava相关的依赖
mvn dependency:tree -Dincludes=com.google.guava:guava
# 将依赖树输出到文件,方便分析
mvn dependency:tree > dependency-tree.txt
# 查看特定深度的依赖
mvn dependency:tree -Dverbose -Dincludes=com.google.guava:guava依赖树输出示例分析:
[INFO] com.example:my-project:jar:1.0.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.0:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.0:compile
[INFO] | | \- ...
[INFO] | \- ...
[INFO] +- com.thirdparty:some-library:jar:1.2.0:compile
[INFO] | \- com.google.guava:guava:jar:19.0:compile
[INFO] \- com.google.guava:guava:jar:31.1-jre:compile这里出现了Guava版本冲突,由于some-library的路径更短,按照最短路径原则,19.0版本会生效。
2. 使用Maven Helper插件(IDEA)
在pom.xml文件底部会显示依赖图,红色表示冲突,可以直接排除。
(四) 依赖冲突解决方案
1. 排除特定传递依赖(Exclusion)
<dependency>
<groupId>com.thirdparty</groupId>
<artifactId>some-library</artifactId>
<version>1.2.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<!-- 也可以使用通配符排除多个 -->
<exclusion>
<groupId>com.google.*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>2. 使用dependencyManagement统一版本管理(推荐)
<properties>
<guava.version>31.1-jre</guava.version>
<spring-boot.version>2.7.0</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 引入Spring Boot BOM,统一管理Spring相关依赖版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 统一管理其他公共依赖 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</dependencyManagement>3. 使用maven-enforcer-plugin强制依赖收敛
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<!-- 禁止重复依赖 -->
<banDuplicatePomDependencyVersions/>
<!-- 强制依赖收敛,发现冲突就构建失败 -->
<dependencyConvergence/>
<!-- 检查依赖版本范围 -->
<requireReleaseDeps>
<message>不允许使用SNAPSHOT版本</message>
</requireReleaseDeps>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>(五) 多模块项目的依赖管理
1. 父POM配置
<!-- parent/pom.xml -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>service</module>
<module>web</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- 统一管理所有子模块的依赖版本 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>2. 子模块配置
<!-- web/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>web-module</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- 不需要写版本号,从父POM继承 -->
</dependency>
</dependencies>
</project>(六) 高级技巧:依赖分析和优化
1. 分析未使用的依赖
mvn dependency:analyze这个命令会分析代码中实际使用的依赖,并报告:
- 已声明但未使用的依赖(Unused declared dependencies)
- 使用的但未声明的依赖(Used undeclared dependencies)
2. 打包时排除重复依赖
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>总结: Maven依赖管理是Java项目的基石。掌握依赖分析、冲突解决和多模块管理,能够显著提升项目的稳定性和可维护性。建立规范的依赖管理流程,是团队协作开发的重要保障。