1718 字
约 5 分钟
6
S06-J04 · Maven 深水区:Profiles、插件、依赖冲突与私服

适用读者:遇到"本地能跑、CI 挂""依赖冲突找不到类""打出的包多了一个版本"的人。
前置:S06-B01(Maven 三层模型与多模块构建)、A02(构建可复现与制品治理)、J01(多模块工程结构)。
覆盖旧文:2891(Maven 构建 Java 项目)、2892(仓库)、2893(依赖管理)、2894(引入外部依赖)、2895(插件)、2896(教程总览)、2897(Maven 构建 Java 项目)、2898(构建与测试)、2899(生命周期)、2900(Profiles)、2901(环境配置)、2902(Archetype)→ 12 篇 / 约 4.5 万字符。
扩展点:E-S06-018(生命周期与插件绑定的因果链:mvn 一条命令到底跑了什么)、E-S06-019(依赖冲突三种表现与 dependencyManagement/enforcer 兜底)、E-S06-020(Profiles 的滥用与"环境隔离的正确落点")。
上一篇:S06-B06 · Java 生态四库实测:JJWT / sensitive-word / tianai-captcha / Commons Lang3
下一篇:S06 系列验收(v2)。


1. 一句话结论

Maven 的行为 = 生命周期阶段 + 绑定的插件执行。看不懂 mvn 跑了什么,就调不动构建;"本地能跑 CI 挂"九成是三处差异:JDK 版本、Profile 激活、依赖解析顺序。

出处:A-2899(生命周期)、A-2895(插件)、A-2900(Profiles)、A-2893(依赖管理)。


2. 生命周期与插件绑定(E-S06-018)

三套生命周期:
clean  : pre-clean → clean → post-clean
default: validate → compile → test → package → verify → install → deploy
site   : pre-site → site → post-site → site-deploy

一条命令执行到某阶段,之前的阶段全部依次执行:
mvn package = validate…compile → test → package(不会执行 verify/install)

默认绑定(jar 打包):

阶段 默认插件目标
process-resources maven-resources-plugin:resources
compile maven-compiler-plugin:compile
test maven-surefire-plugin:test
package maven-jar-plugin:jar
install maven-install-plugin:install

自定义绑定(把插件挂到阶段上):

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <id>print-version</id>
      <phase>validate</phase>          <!-- 挂到哪个阶段 -->
      <goals><goal>run</goal></goals>
      <configuration><target><echo message="${project.version}"/></target></configuration>
    </execution>
  </executions>
</plugin>

判据:"为什么我的插件没跑"= 没绑到阶段,或用了 mvn 未到达该阶段。用 mvn -X 或 mvn help:describe 定位。

正确姿势(可复现构建):

mvn -B -q -DskipTests package        # CI:批处理模式、安静、跳测试
mvn -B verify -Pit                   # 集成测试 profile
mvn dependency:tree -Dincludes=com.foo:bar   # 查某个依赖从哪来
mvn -X compile 2>&1 | grep -A3 'Goal'        # 看实际执行的插件目标

3. 依赖冲突的三种表现与兜底(E-S06-019)

表现 症状 定位
版本仲裁 运行时用的是"较近/先声明"的旧版本 mvn dependency:tree -Dverbose 看 omitted for conflict
传递依赖缺失 NoClassDefFoundError(编译过、运行挂) mvn dependency:tree 对比声明
包冲突 NoSuchMethodError、LinkageError 常见于 javax vs jakarta、log4j vs log4j2

三类修法与优先级:

① dependencyManagement(推荐):在父 POM 统一版本,子模块不写版本
② exclusions:排除不需要的传递依赖(谨慎,可能连带丢必需类)
③ 直接声明版本(最暴力):覆盖仲裁结果,但会掩盖真实冲突
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.17.2</version>     <!-- 一处定版,全工程生效 -->
    </dependency>
  </dependencies>
</dependencyManagement>

兜底(防止别人以后又引歪):

<plugin>
  <artifactId>maven-enforcer-plugin</artifactId>
  <executions><execution>
    <id>enforce</id><goals><goal>enforce</goal></goals>
    <configuration><rules>
      <dependencyConvergence/>       <!-- 冲突即失败 -->
      <requireJavaVersion><version>[17,)</version></requireJavaVersion>
      <banDuplicatePomDependencyVersions/>
    </rules></configuration>
  </execution></executions>
</plugin>

判据:dependencyConvergence 让冲突在构建期暴露,而不是上线后 NoSuchMethodError。引入它会暴露一批历史问题——这是好事,分批修。


4. Profiles:正确落点与三种滥用(E-S06-020)

Profile 能做:改属性、改依赖、改插件配置、改 <repositories>。
Profile 不该做:把"环境差异"塞进 POM(生产者的配置不该由构建产物决定)。

<profiles>
  <profile>
    <id>it</id>
    <activation><activeByDefault>false</activeByDefault></activation>
    <build><plugins>
      <plugin><artifactId>maven-failsafe-plugin</artifactId>
        <executions><execution><goals><goal>integration-test</goal><goal>verify</goal></goals></execution></executions>
      </plugin>
    </plugins></build>
  </profile>
</profiles>
滥用 后果 正确落点
dev/prod 两个 profile 打包不同 jar 制品不可复现(同一 commit 产两种包) 一份制品 + 外部配置(env/ConfigMap/启动参数)
profile 里写数据库密码 密码进仓库与制品 环境变量/密钥管理
靠 activeByDefault 控制行为 命令行 -P 会覆盖,行为不一致 显式 -P<id>,CI 固定写出

判据:"构建期决定环境"是可复现性最大的敌人——同一 commit 必须产出同一制品;环境差异只在启动配置层。


5. 仓库与私服(2892/2901)

本地仓库 ~/.m2/repository → 私服(Nexus/Artifactory)→ 中央仓库
settings.xml 里配 mirror + server 凭据;不要在 POM 里写 <repositories> 指向内网地址
问题 原因 处置
CI 拉不到依赖 私服地址/凭据只在某人 settings.xml 用 CI 的 settings 模板 + 凭据注入
每次构建都下载 无本地缓存或 -U 滥用 CI 缓存 ~/.m2;只在需要更新 SNAPSHOT 时 -U
制品被覆盖 用了 SNAPSHOT 做发布 发布只用 release 版本 + 不可变仓库策略
构建不可复现 依赖了 LATEST/RELEASE/范围版本 全部固定版本号

Archetype(2902)的现代替代:脚手架更适合做成"公司模板仓库 + 参数化脚本",而非维护 archetype-catalog(创建慢、升级难、IDE 兼容差)。


6. 反例:七种"构建不稳定"

现象 根因
本地成功、CI 失败 JDK 版本不同(source/target 与运行 JRE 不一致)
同一 commit 两个包 靠 profile 打包不同环境配置
NoSuchMethodError 传递依赖仲裁到旧版本
构建时长忽长忽短 未缓存 ~/.m2,或每次都 -U
别人机器编不过 依赖了本地 install 的 SNAPSHOT
打包多出测试类 maven-jar-plugin 未排除,或 test-jar 误引
时间戳导致不可复现 未开 project.build.outputTimestamp

7. 自测

  1. mvn package 实际执行哪些阶段?为什么 install 不会跑?
  2. 插件"没跑"的两个原因?
  3. 依赖冲突三种表现与三类修法?
  4. dependencyConvergence 解决什么?
  5. 为什么"用 profile 打包不同环境"是错误的?
答案
  1. validate→…→test→package;生命周期按阶段顺序执行,未指定 install 就不执行。
  2. 未绑定到阶段;命令未到达该阶段。
  3. 版本仲裁/传递缺失/包冲突;dependencyManagement、exclusions、直接声明。
  4. 构建期暴露版本冲突,避免运行期 NoSuchMethodError。
  5. 同一 commit 产出不同制品,破坏可复现性;环境差异应在启动配置层。

8. 下一篇

S06 系列验收(v2) —— 13 篇 / 1,901 点位 / 16 篇尾部旧文 100% 登记 / 下一系列(P16:S15 14 篇)前置依赖。


本篇属于系列《S06 · Java 工程化与组件》。旧文来源:A-2891–2902(Maven 教程 12 篇);扩展点 E-S06-018/025/026。库版本口径为 2026-11 时点,具体小版本 API 以官方文档为准(待核)。

S06-J04 · Maven 深水区:Profiles、插件、依赖冲突与私服
https://www.clxhxhhr.top/posts/5264/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。