Maven 生命周期有哪些阶段?执行命令时到底跑了什么?
Maven 生命周期可以理解为一条已经排好顺序的构建流水线。你输入的不是“执行某个脚本”,而是告诉 Maven 要跑到哪个阶段,Maven 会把这个阶段之前的阶段一起执行。最常用的是 default 生命周期,另外还有 clean 生命周期和 site 生命周期,它们彼此独立。
clean 生命周期负责清理构建产物,典型命令是 mvn clean,会删除 target 目录。default 生命周期负责从校验、编译、测试到打包、安装、发布的主流程。site 生命周期负责生成项目站点文档,业务项目里用得少,但开源库发布文档时仍然有价值。
default 生命周期里高频阶段包括 validate、compile、test、package、verify、install、deploy。执行 mvn package 会先跑到测试再打包,执行 mvn install 会在打包后把产物安装到本地仓库,执行 mvn deploy 则继续上传到远程仓库。
bashmvn clean mvn test mvn clean package mvn clean install -DskipTests mvn clean deploy -Prelease
命令执行时要看清边界
Maven 命令由生命周期阶段、插件目标、参数和 profile 共同决定。mvn clean package 里既有 clean 生命周期,也有 default 生命周期的 package 阶段;mvn dependency:tree 则是直接执行插件目标,不会自动跑完整编译流程。理解这个区别后,很多“为什么这个命令没有编译代码”的疑问就能解释清楚。
CI 里通常不会只跑 package,而会选择 verify 或 deploy。verify 更适合质量门禁,因为它给集成测试、静态检查、覆盖率校验留了位置;deploy 则适合发布流水线,前提是仓库凭证和版本策略已经配置好。取舍点是本地越快越好,CI 越确定越好,所以两边命令不必完全一样。
还有一个容易忽略的点是 profile。相同的 mvn clean package,加上 -Pprod 后可能启用不同资源、插件或依赖,最终产物也会不同。排查构建差异时,要同时记录 Maven 版本、JDK 版本、命令参数和激活的 profile,不要只说“我也是 package”。
如果项目里有集成测试,还要区分 surefire 和 failsafe。前者通常跑单元测试,后者更适合绑定到 integration-test 和 verify。这样可以把快速反馈和完整验证拆开,避免每次本地小改动都等待慢测试。本地调试时可以先用较短阶段确认问题,合并前再交给 CI 跑完整验证。
追问
mvn package、install 和 deploy 有什么区别?
package 只是在当前项目里生成 jar、war 等产物,通常放在 target 目录下。install 会把产物安装到本机 Maven 仓库,供本机其他项目依赖,所以多模块以外的本地联调经常需要它。deploy 会把产物发布到远程仓库,影响团队其他人和 CI 环境,边界更重。踩坑点是把 install 当成发布,结果别人机器上根本拿不到你的包。
为什么执行某个阶段会连前面的阶段一起跑?
生命周期的设计就是保证构建状态可靠,不能在没有编译的情况下直接测试,也不能在没测试的情况下默认打包。比如 mvn verify 会跑过 validate、compile、test、package 等前置阶段。这个规则减少了遗漏步骤,但也带来耗时问题,所以本地开发会用 -DskipTests 或 -DskipITs 做取舍。注意跳过测试只适合本地快速验证,CI 主线不建议长期这么做。
生命周期阶段和 Maven 插件是什么关系?
阶段本身只是一个抽象节点,真正干活的是绑定到阶段上的插件 goal。比如 jar 项目的 compile 阶段通常由 maven-compiler-plugin:compile 执行,test 阶段由 maven-surefire-plugin:test 执行。你也可以直接运行插件目标,例如 mvn dependency:tree,它不一定属于完整生命周期。常见踩坑是以为改了生命周期就等于改了插件行为,实际上很多细节要在插件配置里改。
xml<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <release>17</release> </configuration> </plugin> </plugins> </build>
clean 和 package 为什么经常一起用?
clean 会删除旧的 target,可以避免历史产物、生成代码或资源文件残留影响结果。package 则负责重新编译测试并打包,两者组合能得到更干净的构建。取舍在于速度:本地频繁改代码时不一定每次都 clean,否则增量编译优势会被浪费。遇到“本地能跑、CI 不能跑”或资源文件莫名不更新时,再用 mvn clean package 排查更合适。
多模块项目执行生命周期有什么边界?
在多模块项目根目录执行命令时,Maven reactor 会按模块依赖顺序跑同一个阶段。用 -pl 可以限制模块范围,用 -am 可以补上被依赖模块,这对大仓库节省时间很有用。边界是 Maven 只理解 POM 里的模块和依赖关系,不会自动知道你运行时通过反射、脚本或配置文件间接依赖了哪个模块。遇到这种隐式依赖,最好把模块依赖显式化,否则局部构建很容易漏东西。