Skip to content

Maven / Gradle 构建 ​

Java 中级 · 工程化

构建工具负责依赖管理、编译、打包、多模块组织。Maven 靠「约定 + XML」简单稳定,Gradle 靠「代码化 + 增量构建」灵活高效。本篇重点讲清 Maven 的依赖传递与仲裁——这是「jar 包冲突」的头号根源。

一、Maven 核心概念 ​

1.1 坐标与依赖 ​

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.0</version>
    <scope>compile</scope>          <!-- 作用域 -->
    <exclusions>                    <!-- 排除传递依赖 -->
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
scope编译测试运行打包典型用途
compile(默认)✅✅✅✅核心依赖
provided✅✅✅❌servlet-api(容器提供)
runtime❌✅✅✅JDBC 驱动
test❌✅❌❌JUnit
system✅✅✅❌本地 jar(不推荐)

二、依赖传递与仲裁(重点) ​

依赖会传递:A 依赖 B,B 依赖 C,则 A 自动拿到 C。这很方便,也是冲突的根源。

2.1 两条仲裁规则 ​

  1. 最短路径优先:A → C(1.0)(深度 1)胜过 A → B → C(2.0)(深度 2),用 1.0。
  2. 先声明优先:深度相同时,pom.xml 里先声明的那条被采用。

2.2 查看与解决冲突 ​

bash
# 打印依赖树,找冲突
mvn dependency:tree

# 分析冲突原因
mvn dependency:analyze

# 看某个依赖为什么被引入
mvn dependency:tree -Dincludes=commons-logging:commons-logging

解决冲突的手段:

xml
<!-- 1. 排除传递依赖 -->
<exclusions><exclusion>...</exclusion></exclusions>

<!-- 2. 直接声明想要的版本(路径最短,优先) -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.3-jre</version>   <!-- 统一版本 -->
        </dependency>
    </dependencies>
</dependencyManagement>

NoSuchMethodError / ClassNotFoundException 的头号原因

运行时报 NoSuchMethodError,多半是依赖冲突——编译期用的是 A 版本,运行期类路径里却被仲裁成了 B 版本,方法签名不匹配。用 mvn dependency:tree 定位,用 dependencyManagement 统一版本。

三、生命周期与插件 ​

Maven 有三套生命周期,最常用的是 default:

text
validate → compile → test → package → verify → install → deploy
命令作用
mvn clean清理 target
mvn compile编译主代码
mvn test跑单元测试
mvn package打成 jar/war
mvn install装进本地仓库
mvn deploy发布到远程仓库/私服

生命周期本身是抽象的,真正干活的是插件:

xml
<build>
    <plugins>
        <!-- 编译插件:指定 JDK 版本 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>17</source>
                <target>17</target>
            </configuration>
        </plugin>
        <!-- Spring Boot 打包插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

四、多模块工程 ​

xml
<!-- 父 pom:统一版本与依赖管理 -->
<groupId>com.demo</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
    <module>common</module>
    <module>service</module>
    <module>web</module>
</modules>
<dependencyManagement>
    <!-- 子模块继承版本,无需重复写 -->
</dependencyManagement>

<!-- 子 pom -->
<parent>
    <groupId>com.demo</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
</parent>
<artifactId>web</artifactId>

为什么要用 dependencyManagement

dependencyManagement 只声明版本不实际引入;子模块引入依赖时省略 version,自动继承。这样全工程版本统一,改一处即可,是解决冲突的官方姿势。

五、Gradle 对比 ​

Gradle 用 Groovy/Kotlin DSL 写构建脚本,是代码而非 XML,更灵活,且增量构建 + 构建缓存更快。

kotlin
// build.gradle.kts
plugins { java; id("org.springframework.boot") version "3.2.0" }
group = "com.demo"; version = "1.0.0"
java { sourceCompatibility = JavaVersion.VERSION_17 }

repositories { mavenCentral() }

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
维度MavenGradle
配置XML(约定强)Groovy/Kotlin DSL
构建速度一般快(增量/缓存/并行)
灵活性中高
学习曲线低中
生态成熟稳定现代(Android/K8s 生态)

六、最佳实践 ​

  • 用 dependencyManagement 统一版本,子模块省略 version。
  • 定期 mvn dependency:tree 排查冲突,exclusions 排掉不需要的传递依赖。
  • provided 不要打进包(servlet-api 等),否则容器里冲突。
  • 私服(Nexus) 缓存依赖 + 分发内部构件,别看不了中央仓库就崩。
  • settings.xml 配镜像(阿里云镜像加速),CI 里可关快照更新。
  • 版本锁定:生产用 release 版本,禁用 -SNAPSHOT 上线。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. Maven 依赖仲裁的两条规则是什么?如果路径深度相同会怎样?
  2. compile、provided、runtime、test 四个 scope 的区别是什么?JDBC 驱动该用哪个?
  3. 线上报 NoSuchMethodError,如何用 Maven 命令定位是哪个依赖冲突?怎么解决?
  4. dependencyManagement 和 dependencies 有什么区别?多模块工程为什么推荐前者?

本节小结

构建工具的核心是依赖管理。Maven 靠 最短路径优先 + 先声明优先 仲裁版本,冲突用 exclusions / dependencyManagement 解决;生命周期由插件驱动;多模块用 parent + dependencyManagement 统一版本。遇到 NoSuchMethodError 先跑 mvn dependency:tree。