Skip to content

第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.exampleorg.springframework
  • artifactId:项目/模块名(如 my-appspring-core
  • version:版本(1.0.02.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
testJUnit
providedServlet API(容器提供)
runtimeJDBC 驱动

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)MavenGradle
配置Makefile(命令式)XML(声明式)Groovy/Kotlin DSL(灵活)
依赖管理手动强(中央仓库)
约定约定优于配置约定+灵活

对 C 程序员:Maven 对应 C 的 Make + 手动 lib 管理,但用 XML 声明 + 中央仓库自动下载依赖,跨项目一致。Gradle 更灵活(Android 默认)。理解 GAV 坐标和生命周期是基础。


💡 九、最佳实践

  1. 版本集中管理:properties + dependencyManagement。
  2. scope 正确:test/provided/runtime 按需。
  3. 多模块用父 pom 统一配置。
  4. release 而非 snapshot 上生产。
  5. 私服 Nexus 管理内部依赖和代理中央仓库。
  6. dependency:tree 排查 依赖冲突。
  7. profiles 区分环境(dev/prod)。

📝 练习预告

完成 练习/Ex45_Maven.java 中的 6 道题:

  1. pom.xml 结构(坐标/依赖/插件)
  2. 依赖范围(scope 对比)
  3. 传递依赖与冲突(排除)
  4. 生命周期命令
  5. 多模块项目结构
  6. 综合:用 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-settings

effective-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 对比