Appearance
第45课:Maven
🎯 学习目标
- 理解 Maven 的作用(依赖管理 + 构建生命周期)
- 掌握 pom.xml 结构(坐标/依赖/插件/打包)
- 掌握依赖范围、传递依赖、冲突解决
- 掌握生命周期与常用命令、多模块项目
- 了解私服 Nexus、profiles
📖 一、概念讲解:Maven 解决什么
1. 没有构建工具前
手动下载 jar 放 lib、手动编译、手动打包。依赖版本混乱、冲突难管、团队协作痛。
2. Maven 的两大核心
- 依赖管理:声明依赖(坐标),Maven 自动从仓库下载、管理传递依赖、解决冲突。
- 构建生命周期:统一 clean/compile/test/package/install 等命令,跨项目一致。
pom.xml(声明) → Maven(解析依赖、执行生命周期) → 构建产物(jar/war)3. 坐标(GAV)
Maven 用 groupId:artifactId:version 唯一标识一个构件:
- groupId:组织/公司(如
com.example、org.springframework) - artifactId:项目/模块名(如
my-app、spring-core) - version:版本(
1.0.0、2.7.0,SNAPSHOT 表开发版)
xml
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>📖 二、pom.xml 结构
xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging> <!-- jar/war/pom -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>6.1.0</spring.version> <!-- 版本集中管理 -->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<scope>compile</scope> <!-- 依赖范围 -->
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</build>
</project>📖 三、依赖范围(scope)
| scope | 编译 | 测试 | 运行 | 典型 |
|---|---|---|---|---|
| compile(默认) | ✅ | ✅ | ✅ | Spring |
| test | ❌ | ✅ | ❌ | JUnit |
| provided | ✅ | ✅ | ❌ | Servlet API(容器提供) |
| runtime | ❌ | ✅ | ✅ | JDBC 驱动 |
scope 决定依赖在何时可用,影响打包(test/provided 不打进 war)。
📖 四、传递依赖与冲突
A 依赖 B,B 依赖 C,则 A 传递依赖 C。问题:C 版本冲突。
冲突解决(最近优先 + 短路径优先):
A → B → C:1.0
A → D → C:2.0
路径长度相同 → Maven 3 用"先声明者优先"。排除依赖:
xml
<dependency>
<groupId>B</groupId><artifactId>B</artifactId>
<exclusions>
<exclusion>
<groupId>C</groupId><artifactId>C</artifactId>
</exclusion>
</exclusions>
</dependency>用 mvn dependency:tree 查看依赖树排查冲突。
📖 五、生命周期与命令
Maven 三套生命周期:
- clean:clean(删 target)
- default:compile → test → package → install(装本地仓库)
- site:site(生成文档)
常用命令:
bash
mvn clean # 清理
mvn compile # 编译
mvn test # 编译+测试
mvn package # 打包(jar/war)
mvn install # 装到本地仓库(供其他项目依赖)
mvn clean package # 清理+打包(常用组合)
mvn dependency:tree # 查看依赖树📖 六、多模块项目
xml
<!-- 父 pom(packaging=pom) -->
<packaging>pom</packaging>
<modules>
<module>domain</module>
<module>service</module>
<module>web</module>
</modules>
<!-- 父 pom 统一版本、依赖管理(dependencyManagement) -->
<!-- 子模块继承父 pom -->
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>service</artifactId>dependencyManagement 统一版本(子模块不写 version)、plugins 插件管理。多模块适合大型项目分层。
⚠️ 七、常见陷阱
陷阱1:依赖冲突运行时类找不到
编译过但运行 ClassNotFoundException,多为依赖版本冲突。用 dependency:tree 排查,排除或统一版本。
陷阱2:scope 错误
JDBC 驱动用 compile(默认)会打进包(冗余),应 runtime。Servlet API 应 provided(容器提供)。
陷阱3:SNAPSHOT 不稳定
SNAPSHOT 是开发版,每次可能变(拉最新),生产用 release 稳定版。
陷阱4:版本不统一
多模块各用不同版本依赖。用 dependencyManagement 统一。
陷阱5:私服未配置
公司内部依赖需私服(Nexus),settings.xml 配置镜像/仓库。
🆚 八、Maven vs Gradle / C 的 Make
| 特性 | Make(C) | Maven | Gradle |
|---|---|---|---|
| 配置 | Makefile(命令式) | XML(声明式) | Groovy/Kotlin DSL(灵活) |
| 依赖管理 | 手动 | 强(中央仓库) | 强 |
| 约定 | 无 | 约定优于配置 | 约定+灵活 |
对 C 程序员:Maven 对应 C 的 Make + 手动 lib 管理,但用 XML 声明 + 中央仓库自动下载依赖,跨项目一致。Gradle 更灵活(Android 默认)。理解 GAV 坐标和生命周期是基础。
💡 九、最佳实践
- 版本集中管理:properties + dependencyManagement。
- scope 正确:test/provided/runtime 按需。
- 多模块用父 pom 统一配置。
- release 而非 snapshot 上生产。
- 私服 Nexus 管理内部依赖和代理中央仓库。
- dependency:tree 排查 依赖冲突。
- profiles 区分环境(dev/prod)。
📝 练习预告
完成 练习/Ex45_Maven.java 中的 6 道题:
- pom.xml 结构(坐标/依赖/插件)
- 依赖范围(scope 对比)
- 传递依赖与冲突(排除)
- 生命周期命令
- 多模块项目结构
- 综合:用 Java 解析 pom 依赖树思路
完成后对比 答案/Sol45.java,查看逐行讲解与多解法。
📖 十、dependencyManagement 与 BOM
dependencyManagement 只管理版本,不会自动引入依赖。
父 pom:
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>子模块:
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>BOM 是一组版本清单:
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>使用 BOM 的价值:
text
统一一整套框架的兼容版本。
减少手动指定版本。
避免 Spring、Jackson、Tomcat 等依赖互相不兼容。📖 十一、生命周期、阶段与插件
Maven 的生命周期由阶段组成,阶段由插件目标执行。
常见阶段:
text
validate
compile
test
package
verify
install
deploy执行 mvn package 时,会从前往后执行到 package:
text
validate -> compile -> test -> package插件示例:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>理解关系:
text
生命周期定义“有哪些阶段”。
插件定义“每个阶段做什么”。
pom.xml 把插件目标绑定到阶段。🛠 十二、依赖冲突排查
最常用命令:
bash
mvn dependency:tree只看某个依赖:
bash
mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind常见冲突表现:
text
NoSuchMethodError。
ClassNotFoundException。
NoClassDefFoundError。
运行时行为和本地不一致。
同一个库被不同框架带入多个版本。解决方式:
text
用 dependencyManagement 统一版本。
排除错误的传递依赖。
升级上游依赖。
使用 BOM。
避免在子模块随意写版本。示例排除:
xml
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>📖 十三、settings.xml 与私服
pom.xml 描述项目,settings.xml 描述本机或 CI 的 Maven 环境。
常见配置:
text
本地仓库路径。
镜像 mirror。
私服账号密码。
profile。
代理。公司常用 Nexus 或 Artifactory:
text
代理 Maven Central,提高下载速度。
存放公司内部 jar。
管理 release 和 snapshot 仓库。
控制依赖来源。不要把私服密码写进项目 pom。凭据应放在用户级或 CI 的 settings 中。
📖 十四、多模块分层建议
常见企业项目结构:
text
parent
├── common
├── domain
├── infrastructure
├── application
└── web依赖方向建议:
text
web 依赖 application。
application 依赖 domain。
infrastructure 实现外部访问。
domain 不依赖 web 和 infrastructure。
common 不应变成垃圾桶。父 pom 管理:
text
Java 版本。
编码。
依赖版本。
插件版本。
仓库配置。
公共构建规则。子模块只声明自己真正需要的依赖,避免“父 pom 统一塞所有依赖”。
🛠 十五、Maven 排查清单
构建失败时按下面顺序排查:
text
JDK 版本是否匹配。
Maven 使用的 settings 是否正确。
依赖是否下载失败。
私服是否可访问。
是否存在依赖冲突。
scope 是否错误。
插件版本是否固定。
多模块构建顺序是否正确。
本地仓库是否缓存了损坏 jar。常用命令:
bash
mvn -v
mvn clean package -U
mvn dependency:tree
mvn help:effective-pom
mvn help:effective-settingseffective-pom 很有价值,它能看到继承父 pom、profile、插件合并后的最终配置。
✅ 十六、掌握标准
学完本课后,应能做到:
text
能解释 GAV 坐标。
能写出基本 pom.xml。
能正确使用 compile、test、provided、runtime。
能用 dependencyManagement 和 BOM 统一版本。
能用 dependency:tree 排查依赖冲突。
能解释生命周期阶段和插件的关系。
能设计简单多模块项目结构。
能区分 pom.xml 和 settings.xml 的职责。Maven 的核心价值是把构建和依赖管理标准化。真正的熟练度体现在能稳定复现构建、定位依赖冲突、控制版本边界。
🎓 下一步
- 第46课:Gradle — Groovy DSL、与 Maven 对比