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

张开发
2026/5/1 7:36:16 15 分钟阅读

分享文章

第二章:Maven进阶篇 — 依赖管理与构建生命周期
目标掌握依赖范围、传递依赖、冲突解决、BOM、生命周期、Profile 与资源过滤能解释并治理真实项目构建行为。目录依赖范围 Scope传递依赖与冲突规则dependencyManagement 与 BOM构建生命周期Profile 机制资源过滤实战 Demomaven-demo-core常见问题与避坑专家面试题1. 依赖范围 ScopeMaven 的scope决定依赖在编译、测试、运行、打包时是否可见。Scope编译测试运行是否传递典型依赖compile是是是是业务库、工具库provided是是否否Servlet API、Lombokruntime否是是是JDBC 驱动test否是否否JUnit、Mockitosystem是是否否本地 jar不推荐import仅用于dependencyManagement---BOM示例dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter/artifactIdscopetest/scope/dependencytest依赖不会进入主代码运行时 classpath因此不能在src/main/java中引用 JUnit 类。2. 传递依赖与冲突规则传递依赖如果 A 依赖 BB 依赖 C那么 A 默认也会获得 C这就是传递依赖。maven-demo-web └── maven-demo-core └── maven-demo-apiweb直接依赖core而core依赖api。因此web可以在运行时获得api。冲突解决规则Maven 依赖冲突主要按两条规则处理规则含义最短路径优先离当前项目最近的依赖版本胜出路径相同先声明优先依赖路径长度相同时先声明的依赖胜出排查命令mvn dependency:tree mvn dependency:tree-Dincludesorg.apache.commons:commons-lang3 mvn dependency:tree-Dverbose排除传递依赖dependencygroupIdcom.example/groupIdartifactIdlegacy-sdk/artifactIdversion1.0.0/versionexclusionsexclusiongroupIdcommons-logging/groupIdartifactIdcommons-logging/artifactId/exclusion/exclusions/dependency排除依赖前必须确认当前运行路径确实不需要它。有替代实现或更高版本。测试覆盖相关功能。3. dependencyManagement 与 BOMdependencyManagement只管理版本不会自动引入依赖。父 POM 中dependencyManagementdependenciesdependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.14.0/version/dependency/dependencies/dependencyManagement子模块中dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactId/dependency子模块不用写版本版本从父 POM 继承。BOM 模式BOM 是 Bill of Materials通常是packagingpom专门管理一组依赖版本。dependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion3.2.5/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagement本 Demo 同时提供maven-demo-bom/用于演示公司内部组件如何输出 BOM。BOM 的企业价值场景BOM 解决方式多个微服务依赖版本不一致统一导入公司 BOMSpring Boot、Spring Cloud 版本兼容复杂使用官方 BOMSDK 对外发布提供 BOM调用方无需逐个写版本漏洞修复BOM 升级一次全项目统一收敛4. 构建生命周期Maven 有三套内置生命周期生命周期作用常见阶段clean清理构建产物pre-clean、clean、post-cleandefault主构建流程validate、compile、test、package、verify、install、deploysite生成项目站点site、site-deploydefault 生命周期关键阶段validate ↓ compile ↓ test ↓ package ↓ verify ↓ install ↓ deploy执行mvn package会从validate一直执行到package。执行mvn test不会执行package。Goal 与 PhasePhase 是生命周期阶段Goal 是插件目标。phase: compile goal : maven-compiler-plugin:compile一个 Phase 可以绑定多个 Goal一个 Goal 也可以手动执行mvn compiler:compile mvn dependency:tree5. Profile 机制Profile 用于在不同环境下切换配置。本 Demo 父 POMprofilesprofileiddev/idactivationactiveByDefaulttrue/activeByDefault/activationpropertiesdemo.environmentlocal/demo.environmentdemo.log.levelDEBUG/demo.log.level/properties/profileprofileidprod/idpropertiesdemo.environmentprod/demo.environmentdemo.log.levelWARN/demo.log.level/properties/profile/profiles激活方式mvn package-Pprodmvn help:active-profiles mvn help:effective-pom-PprodProfile 适合切换构建参数不建议把大量业务配置写进 POM。Spring Boot 应用的运行配置仍应优先使用application-dev.yml、环境变量或配置中心。6. 资源过滤资源过滤可以把 Maven 属性写入资源文件。maven-demo-core/pom.xmlbuildresourcesresourcedirectorysrc/main/resources/directoryfilteringtrue/filtering/resource/resources/buildbuild-info.propertiesartifactId${project.artifactId} version${project.version} environment${demo.environment} logLevel${demo.log.level}构建后target/classes/build-info.properties会变成artifactIdmaven-demo-core version1.0.0-SNAPSHOT environmentlocal logLevelDEBUG验证cdmaven-demo mvn-plmaven-demo-core clean packagecatmaven-demo-core/target/classes/build-info.properties7. 实战 Demomaven-demo-core目标演示父 POM 统一依赖版本。maven-demo-core不写依赖版本。Profile 注入环境信息。资源过滤生成构建元数据。关键代码GreetingServicepublicGreetingResponsegreet(GreetingRequestrequest){StringnameStringUtils.capitalize(request.normalizedName());returnnewGreetingResponse(Hello, name,buildInfo.environment(),buildInfo.version());}运行cdmaven-demo mvn-plmaven-demo-coretestmvn-plmaven-demo-core package-Pprodcatmaven-demo-core/target/classes/build-info.properties预期输出environmentprod logLevelWARN8. 常见问题与避坑问题原因解决方案子模块依赖没写版本却能构建父 POMdependencyManagement管理了版本用mvn help:effective-pom查看dependencyManagement写了依赖但没生效它只管版本不自动引入依赖子模块仍要写dependencyProfile 没切换未使用-P或被默认 Profile 覆盖mvn help:active-profiles资源过滤后中文乱码编码未统一设置project.build.sourceEncodingUTF-8systemscope 依赖丢失system 依赖不传递且不推荐发布到私服或本地仓库9. 专家面试题Q1dependencyManagement和dependencies的区别是什么答dependencies会真实引入依赖dependencyManagement只提供版本、scope、exclusion 等默认值不会自动加入 classpath。子模块声明同一依赖但不写版本时才会继承管理信息。企业项目通常在父 POM 或 BOM 中做dependencyManagement在具体模块中按需声明dependencies。Q2Maven 如何解决同一个依赖多个版本冲突答Maven 使用 nearest definition 策略即依赖路径最短的版本优先。如果路径长度相同先声明的依赖优先。这个机制可预测但不一定符合业务期望因此企业项目要用dependencyManagement主动锁定关键依赖版本并用mvn dependency:tree做验证。Q3为什么 BOM 的 scope 是 import答importscope 只能出现在dependencyManagement中用于把另一个 POM 的依赖管理清单导入当前项目。它不会把 BOM 作为普通 jar 加入 classpath因为 BOM 的价值是版本清单不是运行时代码。Q4Profile 适合解决什么问题不适合解决什么问题答Profile 适合切换构建参数、资源过滤、插件执行、发布仓库等构建期差异。不适合承载大量运行期业务配置因为 POM 变更需要重新构建生产配置也不应写死在代码仓库中。10. 进阶篇扩展核查依赖治理深水区10.1 optional 依赖optionaltrue表示该依赖对当前模块编译可见但不会传递给下游项目。dependencygroupIdcom.example/groupIdartifactIdfeature-extension/artifactIdversion1.0.0/versionoptionaltrue/optional/dependency典型场景一个 SDK 支持 Redis、Kafka、JDBC 多种扩展但调用方只使用其中一种。框架模块希望编译期适配某个库但不强迫所有使用方引入它。减少传递依赖污染降低冲突概率。判断标准如果下游不一定需要这个依赖就考虑 optional如果下游运行一定需要就不要 optional。10.2 classifier 与 type同一个 GAV 下可能有多个附属产物classifier用来区分这些产物。dependencygroupIdcom.example/groupIdartifactIddemo-sdk/artifactIdversion1.0.0/versionclassifiertests/classifiertypetest-jar/type/dependency常见组合用途classifiertype源码包sourcesjava-sourceJavadocjavadocjavadoc测试工具包teststest-jar原生平台包linux-x86_64jar企业发布 SDK 时建议同时发布主 JAR、源码包和 Javadoc便于调用方调试和审计。10.3 exclusions 的边界排除依赖不是越多越好。错误排除会导致运行期ClassNotFoundException或行为缺失。排除前要做三步mvn dependency:tree-DincludesgroupId:artifactId mvntestmvn-plaffected-module-amverify推荐记录排除原因exclusion!-- 使用 spring-jcl 替代 commons-logging避免日志桥接冲突 --groupIdcommons-logging/groupIdartifactIdcommons-logging/artifactId/exclusion10.4 依赖收敛依赖收敛指同一个依赖在整棵依赖树中只出现一个版本。大型项目最容易出现 Jackson、Netty、Guava、SLF4J、Micrometer 等依赖版本漂移。可以使用 EnforcerrequireUpperBoundDeps/dependencyConvergence/但注意严格收敛在复杂 Spring Boot 项目中可能产生大量误报需要结合 BOM 和排除策略逐步治理。10.5 运行时 classpath 与编译时 classpathcompile成功不代表运行成功。原因是编译 classpath 和运行 classpath 不完全一样。例子Scope编译可见运行可见compile是是provided是否runtime否是test测试可见主运行不可见如果你把 JDBC 驱动写成provided编译可能成功但运行连接数据库时会找不到 Driver。10.6 Profile 激活方式详解Profile 可以通过多种方式激活方式示例适用场景命令行mvn package -Pprod手动切环境默认激活activeByDefaulttrue/activeByDefault本地开发默认值JDK 激活jdk[17,)/jdkJDK 版本差异OS 激活osfamilymac/family/os操作系统差异属性激活-DenvprodCI 参数驱动文件激活文件存在时激活本地私有配置排查命令mvn help:active-profiles mvn help:all-profiles一个容易踩的坑只要显式激活了同一个 POM 中的其他 ProfileactiveByDefaultProfile 就可能不再激活。因此关键默认值不要只放在activeByDefaultProfile 中建议在基础properties中提供默认值再由dev/test/prod覆盖。Demo 的demo.environmentlocal和demo.log.levelDEBUG就采用这种方式避免执行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.skiptrue否否可能掩盖测试代码已损坏推荐本地临时打包用-DskipTests。CI 主线不要跳测试。发布流水线至少执行verify。10.10 进阶实操任务任务命令目标查看 Web 依赖树mvn -pl maven-demo-web -am dependency:tree理解传递依赖切换生产 Profilemvn -pl maven-demo-core package -Pprod验证资源过滤查看 Effective POMmvn help:effective-pom -Pprod验证 Profile 合并只构建 CLI 和上游mvn -pl maven-demo-cli -am package验证 Reactor分析依赖使用mvn dependency:analyze识别未声明/未使用依赖10.11 进阶篇新增面试题Q5optional 和 exclusion 的区别是什么答optional是依赖提供方声明“这个依赖不要自动传递给下游”exclusion是依赖使用方声明“我不接受某个传递依赖”。前者由上游控制后者由下游控制。optional 适合框架扩展能力exclusion 适合解决冲突或替换实现。Q6为什么mvn verify比mvn package更适合 CI答package只保证产物生成verify会执行到更靠后的验证阶段适合绑定集成测试、质量检查、覆盖率门禁和安全扫描。CI 的目标不是只打包而是证明代码满足质量标准。Q7BOM 和 Parent POM 是否可以互相替代答不能完全替代。Parent POM 通过继承管理插件、属性、Profile 和构建规则BOM 通过 import 管理依赖版本。多仓库项目可能不能共享同一个 Parent但仍可统一导入公司 BOM。

更多文章