第二章:Maven进阶篇 — 依赖管理与构建生命周期

目标:掌握依赖范围、传递依赖、冲突解决、BOM、生命周期、Profile 与资源过滤,能解释并治理真实项目构建行为。


目录

  1. [依赖范围 Scope](#依赖范围 Scope)
  2. 传递依赖与冲突规则
  3. [dependencyManagement 与 BOM](#dependencyManagement 与 BOM)
  4. 构建生命周期
  5. [Profile 机制](#Profile 机制)
  6. 资源过滤
  7. [实战 Demo:maven-demo-core](#实战 Demo:maven-demo-core)
  8. 常见问题与避坑
  9. 专家面试题

1. 依赖范围 Scope

Maven 的 scope 决定依赖在编译、测试、运行、打包时是否可见。

Scope 编译 测试 运行 是否传递 典型依赖
compile 业务库、工具库
provided Servlet API、Lombok
runtime JDBC 驱动
test JUnit、Mockito
system 本地 jar,不推荐
import 仅用于 dependencyManagement - - - BOM

示例:

xml 复制代码
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>

test 依赖不会进入主代码运行时 classpath,因此不能在 src/main/java 中引用 JUnit 类。


2. 传递依赖与冲突规则

传递依赖

如果 A 依赖 B,B 依赖 C,那么 A 默认也会获得 C,这就是传递依赖。

text 复制代码
maven-demo-web
  └── maven-demo-core
      └── maven-demo-api

web 直接依赖 core,而 core 依赖 api。因此 web 可以在运行时获得 api

冲突解决规则

Maven 依赖冲突主要按两条规则处理:

规则 含义
最短路径优先 离当前项目最近的依赖版本胜出
路径相同先声明优先 依赖路径长度相同时,先声明的依赖胜出

排查命令:

bash 复制代码
mvn dependency:tree
mvn dependency:tree -Dincludes=org.apache.commons:commons-lang3
mvn dependency:tree -Dverbose

排除传递依赖

xml 复制代码
<dependency>
  <groupId>com.example</groupId>
  <artifactId>legacy-sdk</artifactId>
  <version>1.0.0</version>
  <exclusions>
    <exclusion>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

排除依赖前必须确认:

  1. 当前运行路径确实不需要它。
  2. 有替代实现或更高版本。
  3. 测试覆盖相关功能。

3. dependencyManagement 与 BOM

dependencyManagement 只管理版本,不会自动引入依赖。

父 POM 中:

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.14.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>

子模块中:

xml 复制代码
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
</dependency>

子模块不用写版本,版本从父 POM 继承。

BOM 模式

BOM 是 Bill of Materials,通常是 packaging=pom,专门管理一组依赖版本。

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>3.2.5</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

本 Demo 同时提供 maven-demo-bom/,用于演示公司内部组件如何输出 BOM。

BOM 的企业价值

场景 BOM 解决方式
多个微服务依赖版本不一致 统一导入公司 BOM
Spring Boot、Spring Cloud 版本兼容复杂 使用官方 BOM
SDK 对外发布 提供 BOM,调用方无需逐个写版本
漏洞修复 BOM 升级一次,全项目统一收敛

4. 构建生命周期

Maven 有三套内置生命周期:

生命周期 作用 常见阶段
clean 清理构建产物 pre-cleancleanpost-clean
default 主构建流程 validatecompiletestpackageverifyinstalldeploy
site 生成项目站点 sitesite-deploy

default 生命周期关键阶段

text 复制代码
validate
  ↓
compile
  ↓
test
  ↓
package
  ↓
verify
  ↓
install
  ↓
deploy

执行 mvn package 会从 validate 一直执行到 package。执行 mvn test 不会执行 package

Goal 与 Phase

Phase 是生命周期阶段,Goal 是插件目标。

text 复制代码
phase: compile
goal : maven-compiler-plugin:compile

一个 Phase 可以绑定多个 Goal,一个 Goal 也可以手动执行:

bash 复制代码
mvn compiler:compile
mvn dependency:tree

5. Profile 机制

Profile 用于在不同环境下切换配置。

本 Demo 父 POM:

xml 复制代码
<profiles>
  <profile>
    <id>dev</id>
    <activation>
      <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
      <demo.environment>local</demo.environment>
      <demo.log.level>DEBUG</demo.log.level>
    </properties>
  </profile>
  <profile>
    <id>prod</id>
    <properties>
      <demo.environment>prod</demo.environment>
      <demo.log.level>WARN</demo.log.level>
    </properties>
  </profile>
</profiles>

激活方式:

bash 复制代码
mvn package -Pprod
mvn help:active-profiles
mvn help:effective-pom -Pprod

Profile 适合切换构建参数,不建议把大量业务配置写进 POM。Spring Boot 应用的运行配置仍应优先使用 application-dev.yml、环境变量或配置中心。


6. 资源过滤

资源过滤可以把 Maven 属性写入资源文件。

maven-demo-core/pom.xml

xml 复制代码
<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
</build>

build-info.properties

properties 复制代码
artifactId=${project.artifactId}
version=${project.version}
environment=${demo.environment}
logLevel=${demo.log.level}

构建后 target/classes/build-info.properties 会变成:

properties 复制代码
artifactId=maven-demo-core
version=1.0.0-SNAPSHOT
environment=local
logLevel=DEBUG

验证:

bash 复制代码
cd maven-demo
mvn -pl maven-demo-core clean package
cat maven-demo-core/target/classes/build-info.properties

7. 实战 Demo:maven-demo-core

目标

演示:

  1. 父 POM 统一依赖版本。
  2. maven-demo-core 不写依赖版本。
  3. Profile 注入环境信息。
  4. 资源过滤生成构建元数据。

关键代码

GreetingService

java 复制代码
public GreetingResponse greet(GreetingRequest request) {
    String name = StringUtils.capitalize(request.normalizedName());
    return new GreetingResponse(
            "Hello, " + name,
            buildInfo.environment(),
            buildInfo.version()
    );
}

运行:

bash 复制代码
cd maven-demo
mvn -pl maven-demo-core test
mvn -pl maven-demo-core package -Pprod
cat maven-demo-core/target/classes/build-info.properties

预期输出:

properties 复制代码
environment=prod
logLevel=WARN

8. 常见问题与避坑

问题 原因 解决方案
子模块依赖没写版本却能构建 父 POM dependencyManagement 管理了版本 mvn help:effective-pom 查看
dependencyManagement 写了依赖但没生效 它只管版本,不自动引入依赖 子模块仍要写 <dependency>
Profile 没切换 未使用 -P 或被默认 Profile 覆盖 mvn help:active-profiles
资源过滤后中文乱码 编码未统一 设置 project.build.sourceEncoding=UTF-8
system scope 依赖丢失 system 依赖不传递且不推荐 发布到私服或本地仓库

9. 专家面试题

Q1:dependencyManagementdependencies 的区别是什么?

答:dependencies 会真实引入依赖;dependencyManagement 只提供版本、scope、exclusion 等默认值,不会自动加入 classpath。子模块声明同一依赖但不写版本时,才会继承管理信息。企业项目通常在父 POM 或 BOM 中做 dependencyManagement,在具体模块中按需声明 dependencies

Q2:Maven 如何解决同一个依赖多个版本冲突?

答:Maven 使用 nearest definition 策略,即依赖路径最短的版本优先。如果路径长度相同,先声明的依赖优先。这个机制可预测但不一定符合业务期望,因此企业项目要用 dependencyManagement 主动锁定关键依赖版本,并用 mvn dependency:tree 做验证。

Q3:为什么 BOM 的 scope 是 import?

答:import scope 只能出现在 dependencyManagement 中,用于把另一个 POM 的依赖管理清单导入当前项目。它不会把 BOM 作为普通 jar 加入 classpath,因为 BOM 的价值是版本清单,不是运行时代码。

Q4:Profile 适合解决什么问题,不适合解决什么问题?

答:Profile 适合切换构建参数、资源过滤、插件执行、发布仓库等构建期差异。不适合承载大量运行期业务配置,因为 POM 变更需要重新构建,生产配置也不应写死在代码仓库中。


10. 进阶篇扩展核查:依赖治理深水区

10.1 optional 依赖

optional=true 表示该依赖对当前模块编译可见,但不会传递给下游项目。

xml 复制代码
<dependency>
  <groupId>com.example</groupId>
  <artifactId>feature-extension</artifactId>
  <version>1.0.0</version>
  <optional>true</optional>
</dependency>

典型场景:

  1. 一个 SDK 支持 Redis、Kafka、JDBC 多种扩展,但调用方只使用其中一种。
  2. 框架模块希望编译期适配某个库,但不强迫所有使用方引入它。
  3. 减少传递依赖污染,降低冲突概率。

判断标准:如果下游不一定需要这个依赖,就考虑 optional;如果下游运行一定需要,就不要 optional。

10.2 classifier 与 type

同一个 GAV 下可能有多个附属产物,classifier 用来区分这些产物。

xml 复制代码
<dependency>
  <groupId>com.example</groupId>
  <artifactId>demo-sdk</artifactId>
  <version>1.0.0</version>
  <classifier>tests</classifier>
  <type>test-jar</type>
</dependency>

常见组合:

用途 classifier type
源码包 sources java-source
Javadoc javadoc javadoc
测试工具包 tests test-jar
原生平台包 linux-x86_64 jar

企业发布 SDK 时,建议同时发布主 JAR、源码包和 Javadoc,便于调用方调试和审计。

10.3 exclusions 的边界

排除依赖不是越多越好。错误排除会导致运行期 ClassNotFoundException 或行为缺失。

排除前要做三步:

bash 复制代码
mvn dependency:tree -Dincludes=groupId:artifactId
mvn test
mvn -pl affected-module -am verify

推荐记录排除原因:

xml 复制代码
<exclusion>
  <!-- 使用 spring-jcl 替代 commons-logging,避免日志桥接冲突 -->
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
</exclusion>

10.4 依赖收敛

依赖收敛指同一个依赖在整棵依赖树中只出现一个版本。大型项目最容易出现 Jackson、Netty、Guava、SLF4J、Micrometer 等依赖版本漂移。

可以使用 Enforcer:

xml 复制代码
<requireUpperBoundDeps/>
<dependencyConvergence/>

但注意:严格收敛在复杂 Spring Boot 项目中可能产生大量误报,需要结合 BOM 和排除策略逐步治理。

10.5 运行时 classpath 与编译时 classpath

compile 成功不代表运行成功。原因是编译 classpath 和运行 classpath 不完全一样。

例子:

Scope 编译可见 运行可见
compile
provided
runtime
test 测试可见 主运行不可见

如果你把 JDBC 驱动写成 provided,编译可能成功,但运行连接数据库时会找不到 Driver。

10.6 Profile 激活方式详解

Profile 可以通过多种方式激活:

方式 示例 适用场景
命令行 mvn package -Pprod 手动切环境
默认激活 <activeByDefault>true</activeByDefault> 本地开发默认值
JDK 激活 <jdk>[17,)</jdk> JDK 版本差异
OS 激活 <os><family>mac</family></os> 操作系统差异
属性激活 -Denv=prod CI 参数驱动
文件激活 文件存在时激活 本地私有配置

排查命令:

bash 复制代码
mvn help:active-profiles
mvn help:all-profiles

一个容易踩的坑:只要显式激活了同一个 POM 中的其他 Profile,activeByDefault Profile 就可能不再激活。因此关键默认值不要只放在 activeByDefault Profile 中,建议在基础 properties 中提供默认值,再由 dev/test/prod 覆盖。Demo 的 demo.environment=localdemo.log.level=DEBUG 就采用这种方式,避免执行 mvn -Pquality verify 时资源过滤变量丢失。

10.7 资源过滤风险

资源过滤很有用,但也有风险。

风险 示例 解决方案
二进制文件被过滤损坏 图片、证书、字体 不要对二进制目录开启 filtering
占位符误替换 ${...} 被 Maven 当变量 使用不同 delimiter 或关闭过滤
密钥进入产物 ${password} 被写入配置 密钥走环境变量或配置中心
多环境配置混乱 POM Profile 和 Spring Profile 混用 明确构建期和运行期边界

10.8 生命周期阶段补全

default 生命周期远不止常见的 7 个阶段。

阶段 作用
validate 校验项目
initialize 初始化构建状态
generate-sources 生成源码
process-sources 处理源码
generate-resources 生成资源
process-resources 复制和过滤资源
compile 编译主代码
process-test-resources 复制测试资源
test-compile 编译测试代码
test 执行单元测试
prepare-package 打包前准备
package 生成产物
pre-integration-test 集成测试前准备
integration-test 集成测试
post-integration-test 集成测试后清理
verify 校验结果
install 安装到本地仓库
deploy 发布到远程仓库

10.9 跳过测试的区别

参数 编译测试代码 执行测试 风险
-DskipTests 能发现测试代码编译错误
-Dmaven.test.skip=true 可能掩盖测试代码已损坏

推荐:

  1. 本地临时打包用 -DskipTests
  2. CI 主线不要跳测试。
  3. 发布流水线至少执行 verify

10.10 进阶实操任务

任务 命令 目标
查看 Web 依赖树 mvn -pl maven-demo-web -am dependency:tree 理解传递依赖
切换生产 Profile mvn -pl maven-demo-core package -Pprod 验证资源过滤
查看 Effective POM mvn help:effective-pom -Pprod 验证 Profile 合并
只构建 CLI 和上游 mvn -pl maven-demo-cli -am package 验证 Reactor
分析依赖使用 mvn dependency:analyze 识别未声明/未使用依赖

10.11 进阶篇新增面试题

Q5:optional 和 exclusion 的区别是什么?

答:optional 是依赖提供方声明"这个依赖不要自动传递给下游";exclusion 是依赖使用方声明"我不接受某个传递依赖"。前者由上游控制,后者由下游控制。optional 适合框架扩展能力,exclusion 适合解决冲突或替换实现。

Q6:为什么 mvn verifymvn package 更适合 CI?

答:package 只保证产物生成,verify 会执行到更靠后的验证阶段,适合绑定集成测试、质量检查、覆盖率门禁和安全扫描。CI 的目标不是只打包,而是证明代码满足质量标准。

Q7:BOM 和 Parent POM 是否可以互相替代?

答:不能完全替代。Parent POM 通过继承管理插件、属性、Profile 和构建规则;BOM 通过 import 管理依赖版本。多仓库项目可能不能共享同一个 Parent,但仍可统一导入公司 BOM。

相关推荐
xun-ming1 小时前
AI时代Java程序员自救手册
java·开发语言·人工智能
DavidSoCool1 小时前
GB28181 PTZCmd 完整指令对照表(8 位 16 进制)+ 详细注释 + 使用说明
java·sip·gb28181
张健11564096481 小时前
C++访问控制与友元
java·开发语言·c++
Sam_Deep_Thinking2 小时前
中小团队需要一个资源微服务
java·微服务·架构
Thanks_ks2 小时前
透过 Copy-On-Write 机制:理解并发编程中的性能与一致性权衡
java·多线程·并发编程·底层原理·写时复制·copyonwrite·性能优
一只幸运猫.2 小时前
JAVA后端面试题
java·开发语言
空中海2 小时前
第三章:Maven高级篇 — 插件开发与多模块工程
java·maven
秋92 小时前
TiDB 数据库全链路实战指南:从下载部署到 Java 高并发调优
java·数据库·tidb
JAVA面经实录9172 小时前
Java开发工程基础完整手册(企业实战完整版)
java·开发语言·git·ci/cd·svn·github·intellij idea