Maven 项目如何接入 CI 才能稳定构建和发布?
Maven 接入 CI 的目标不是把本地命令搬到流水线上,而是让每次提交都在干净、可重复、可追踪的环境里完成编译、测试、打包和必要的检查。好的 Maven CI 会固定 JDK 和 Maven 版本,缓存依赖但不污染构建,上传测试报告和构建产物,并把 deploy 限制在受保护分支或手动发布阶段。做不到这些,流水线就只是另一台“同事电脑”。
最小可用的 Maven CI
GitHub Actions 可以从这份配置开始:
yamlname: Maven CI on: push: branches: [ main, master, develop ] pull_request: branches: [ main, master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: '17' cache: maven - name: Build and test run: ./mvnw -B --no-transfer-progress clean verify - name: Upload test reports if: always() uses: actions/upload-artifact@v4 with: name: surefire-reports path: '**/target/surefire-reports/*.xml'
如果项目还没加 Maven Wrapper,也可以用 mvn -B clean verify,但长期建议改成 ./mvnw。verify 比 test 更适合作为默认阶段,因为它会覆盖集成测试、打包前检查和部分插件绑定目标。
GitLab CI 里要注意本地仓库路径和缓存:
yamlstages: [build, test, package] variables: MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository" cache: key: maven-${CI_COMMIT_REF_SLUG} paths: - .m2/repository build: image: maven:3.9.6-eclipse-temurin-17 stage: build script: - mvn -B --no-transfer-progress clean verify artifacts: when: always reports: junit: '**/target/surefire-reports/TEST-*.xml' paths: - target/*.jar
Jenkins 用 Docker agent 时,最好挂载 Maven 仓库缓存,但不要让多个项目共享到互相污染:
groovypipeline { agent { docker { image 'maven:3.9.6-eclipse-temurin-17' args '-v $WORKSPACE/.m2:/root/.m2' } } stages { stage('Verify') { steps { sh 'mvn -B --no-transfer-progress clean verify' } } } post { always { junit '**/target/surefire-reports/*.xml' } } }
发布阶段要和普通 CI 分开。PR 只跑 verify,主分支可以打包并上传 artifact,deploy 应只在 tag、release 分支或人工审批后执行。涉及私服发布时,用 CI Secret 生成临时 settings.xml,不要把账号密码写进仓库。
质量检查可以按成本分层。每个 PR 都跑单元测试、编译和基础静态检查;依赖漏洞扫描、集成测试、性能测试可以放在主分支或夜间任务。这样反馈速度和质量保障能兼顾,不会因为流水线太慢让开发者绕开它。Maven 里可以把慢测试绑定到 Failsafe,再通过 profile 控制是否执行,例如 PR 跳过外部依赖集成测试,合并后再跑完整验证。
发布制品时还要区分 snapshot 和 release。普通分支可以部署到 snapshot 仓库,正式 tag 才允许部署到 releases 仓库。distributionManagement 负责声明地址,CI 条件负责限制何时执行 mvn deploy。如果公司要求制品可追溯,还可以在构建时写入 Git commit、构建号和时间,但这些信息最好进入 manifest 或 build-info 文件,不要影响依赖坐标本身。
流水线还应该保留足够的失败现场。测试报告、构建日志、关键产物和覆盖率报告最好在失败时也上传,否则排查只能重新跑。对于偶发失败,保留 surefire dump、failsafe report 和容器日志尤其重要。CI 的价值不只是挡住坏代码,也要让失败原因尽快暴露。
追问
CI 中应该用 clean install 还是 clean verify?
多数项目默认用 clean verify 更合适。install 会把产物写进本地仓库,可能掩盖模块依赖声明不完整的问题,也会污染缓存。只有后续步骤确实需要从本地仓库解析当前项目产物时,才考虑 install。常见坑是上一次流水线留下的本地 SNAPSHOT 被复用,本次代码其实没构建对也能通过。
Maven 依赖缓存会不会带来风险?
会,但可以控制。缓存能显著减少下载时间,尤其是多模块项目和频繁 PR 构建;风险是缓存损坏、SNAPSHOT 过期或不同分支互相影响。取舍上,release 构建应更保守,必要时清理 SNAPSHOT 或使用独立 cache key。踩坑点是缓存整个 .m2 后私服里已删除的坏包仍被 CI 使用,导致问题复现不了。
多模块项目如何加快 CI?
可以用 Maven 并行构建,比如 mvn -T 1C clean verify,也可以根据变更模块做增量构建。并行适合测试隔离做得好的项目,增量适合模块边界清晰的大仓。边界是不要为了速度牺牲可信度,公共模块、父 POM 或依赖管理变化时必须扩大构建范围。常见坑是测试共用端口、临时目录或数据库,并行后偶发失败,看起来像代码不稳定,其实是测试隔离差。
CI 里如何处理私服账号和 settings.xml?
账号密码应放在 CI Secret,通过模板生成 settings.xml,或者使用平台提供的 Maven server 配置。POM 只写 repository id 和 distributionManagement,不写凭据。取舍是配置稍麻烦,但安全边界清楚,离职和轮换密钥也容易处理。坑在于 settings 里的 server id 和 POM 里的 repository id 不一致,deploy 阶段才报 401。
测试、质量检查和发布应该放在同一条流水线吗?
可以在同一个 workflow 里分 job,但触发条件要分开。PR 阶段跑编译、单测、静态检查;主分支可以产出制品;发布需要 tag、手动触发或审批。这样既不拖慢每个 PR,也能保证发布入口受控。常见坑是 push 到任意分支都执行 mvn deploy,轻则私服堆满无意义版本,重则把未审核代码发布出去。
小结
Maven CI 的稳定性来自几条基本规则:固定 JDK 和 Maven,优先跑 clean verify,缓存依赖但隔离发布,报告测试结果,凭据只走 Secret。流水线越复杂,越要把“验证”和“发布”分开,否则出问题时很难判断是代码失败、环境失败,还是发布策略本身有漏洞。