18、持续集成:CI/CD集成(Jenkins/GitHub Actions)、Gradle Wrapper、环境配置、并行任务

持续集成,说白了就是让机器替你跑那些重复的活儿。我见过太多团队,每天手动打包、手动测试、手动部署,结果呢?要么漏了步骤,要么环境不一致,最后上线前手忙脚乱。嗯,这一章我们就聊聊怎么用 Gradle 把 CI/CD 这件事做得漂亮。

18.1 Gradle Wrapper:团队的“统一语言”

先问一个问题:你的项目里是不是每个人用的 Gradle 版本都不一样?有人用 7.0,有人用 8.5,还有人压根没装 Gradle?

我个人习惯,所有项目必须带上 Gradle Wrapper。它其实就两个文件:gradlew(Shell 脚本)和 gradlew.bat(Windows 批处理),外加一个 gradle/wrapper/gradle-wrapper.properties。你只要把这三个东西提交到 Git,任何人 clone 下来直接 ./gradlew build 就行,版本完全一致。

核心配置:

# gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

我在项目中遇到过一件事:有个新同事用系统自带的 Gradle 6.8 跑构建,结果插件不兼容,报了一堆错。折腾了半天才发现是版本问题。从那以后,我要求所有 CI 脚本里必须用 ./gradlew,而不是 gradle。你想想看,这能省多少排查时间?

小技巧:如果你要升级 Gradle 版本,直接改 gradle-wrapper.properties 里的 distributionUrl 就行。或者运行 ./gradlew wrapper --gradle-version=8.5,它会自动帮你更新。

18.2 环境配置:别让“在我机器上能跑”成为借口

CI/CD 最怕什么?环境不一致。本地能跑,CI 上挂了;CI 能跑,生产环境又挂了。说白了,就是配置没管好。

我建议用 gradle.properties 配合环境变量来管理配置。举个例子:

# gradle.properties(公共配置)
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true

# 环境特定配置,通过 -P 参数传入
# ./gradlew build -Penv=production

然后在 build.gradle 里这样读取:

def env = project.findProperty('env') ?: 'development'
println "当前环境: $env"

if (env == 'production') {
    // 生产环境特殊处理
    apply from: 'production.gradle'
}

我曾经踩过一个坑:测试环境用 H2 数据库,生产环境用 MySQL,结果有个 SQL 方言在 H2 上没问题,到 MySQL 就报错。后来我强制 CI 里所有环境都用 Docker 容器跑,数据库、缓存、消息队列全部容器化。嗯,从那以后“在我机器上能跑”这句话就再也没出现过了。

注意:不要把敏感信息(如密码、API Key)写进 gradle.properties。用环境变量或者 CI 平台的 Secrets 功能来管理。Jenkins 有 Credentials Binding 插件,GitHub Actions 有 Secrets 功能。

18.3 Jenkins 集成:老牌 CI 工具的 Gradle 实践

Jenkins 虽然界面有点“复古”,但胜在稳定、插件生态丰富。我用了好几年 Jenkins,总结了一套比较顺手的配置方式。

首先,Jenkins 节点上要确保有 JDK 和 Git。Gradle 不需要单独安装,因为我们会用 Wrapper。Pipeline 脚本大致长这样:

pipeline {
    agent any

    tools {
        jdk 'JDK17'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                sh './gradlew clean build'
            }
        }

        stage('Test') {
            steps {
                sh './gradlew test'
            }
            post {
                always {
                    junit 'build/reports/tests/**/*.xml'
                }
            }
        }

        stage('Publish') {
            when {
                branch 'main'
            }
            steps {
                sh './gradlew publish'
            }
        }
    }

    post {
        failure {
            emailext(
                subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: "请查看构建日志: ${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
    }
}

我个人习惯把 ./gradlew 命令封装成函数,方便复用。比如:

def gradle(String... args) {
    sh "./gradlew ${args.join(' ')}"
}

// 使用
stage('Build') {
    steps {
        gradle('clean', 'build', '-x', 'test')
    }
}

Jenkins 关键点:

  • 使用 Pipeline 插件,不要用 Freestyle 项目(维护性差)
  • 把 Jenkinsfile 放在代码仓库里,而不是 Jenkins 服务器上
  • junit 步骤收集测试报告,方便查看趋势
  • 构建产物(JAR/WAR)用 Archive Artifacts 保存

18.4 GitHub Actions:云原生 CI 的轻量选择

如果你的项目托管在 GitHub 上,GitHub Actions 是个很香的选择。它不需要自己维护服务器,配置也简单。

一个典型的 Gradle 工作流文件 .github/workflows/build.yml

name: Java CI with Gradle

on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Setup Gradle
      uses: gradle/actions/setup-gradle@v3

    - name: Build with Gradle
      run: ./gradlew build

    - name: Upload build reports
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: test-reports
        path: build/reports/tests/

你可能会问:为什么不用 gradle build 而要用 ./gradlew?因为 GitHub Actions 的 Runner 上不一定装了 Gradle,但 Wrapper 脚本是自包含的。而且 gradle/actions/setup-gradle 这个 Action 会自动缓存依赖,第二次构建能快不少。

我在项目中遇到过一个问题:GitHub Actions 默认的 Runner 只有 2 核 CPU,跑并行任务时反而更慢。后来我调整了并行度,才把构建时间降下来。这个我们后面会细说。

GitHub Actions 小技巧:

  • matrix 策略跑多版本 JDK 测试
  • cache 缓存 Gradle 的 ~/.gradle/caches 目录
  • if: always() 确保即使构建失败也能上传测试报告

18.5 并行任务:让构建飞起来

Gradle 默认是串行执行任务的。但现代机器都是多核的,不利用起来太浪费了。我见过一个项目,单模块构建要 15 分钟,开启并行后直接降到 6 分钟。

开启并行很简单,在 gradle.properties 里加一行:

org.gradle.parallel=true

但要注意,并行不是银弹。它只对独立模块有效。如果你的项目是单模块,或者模块之间有强依赖关系,并行效果就不明显。

更细粒度的控制可以用 --max-workers 参数:

./gradlew build --max-workers=4

这个值一般设为 CPU 核心数减 1。为什么?因为要留一个核心给操作系统和其他进程。我曾经在 8 核机器上设成 8,结果构建时系统卡得不行,鼠标都动不了。嗯,后来学乖了。

并行任务配置建议:

CPU 核心数 建议 max-workers 适用场景
2 2 小型项目,单模块
4 3 中型项目,多模块
8 6 大型项目,多模块
16 12 超大型项目,微服务

除了模块级别的并行,Gradle 还支持任务级别的并行。比如你的项目里有多个独立的测试任务,可以这样配置:

tasks.withType(Test).configureEach {
    maxParallelForks = 2
    forkEvery = 50  // 每 50 个测试用例重启一次 JVM
}

这样测试任务就能并行执行了。但要注意,如果测试用例之间有共享资源(比如数据库),并行可能会导致冲突。我一般会在 CI 环境里用独立的数据库实例,或者用 Testcontainers 做隔离。

并行任务的坑:

  • 并行构建时,日志会交错输出,排查问题比较麻烦。建议用 --console=plain 让输出更清晰。
  • 如果模块之间有 compileOnly 依赖,并行构建可能会报“找不到符号”的错误。这时候需要检查依赖配置是否正确。
  • 并行构建会消耗更多内存。如果 CI 机器的内存不够,建议降低 max-workers

18.6 知识体系总览

下面这张图总结了本章的核心内容,从 CI/CD 集成到并行任务优化,你可以对照着梳理自己的构建流程。

CI/CD 集成与 Gradle 构建优化 Gradle Wrapper 环境配置 CI/CD 集成 并行任务 gradlew / gradlew.bat gradle-wrapper.properties gradle.properties 环境变量 / Secrets Jenkins Pipeline GitHub Actions 模块级并行 任务级并行 核心目标:统一环境 + 自动化构建 + 高效并行 减少人为错误 · 提升构建速度 · 保障交付质量 实践建议 1. 所有项目必须使用 Gradle Wrapper,禁止直接调用系统 Gradle 2. CI 脚本中显式指定 JDK 版本,避免环境不一致 3. 根据 CPU 核心数合理设置并行度,不要盲目追求最大并行

这张图把本章的四个核心模块串起来了。你从 Gradle Wrapper 开始,确保团队统一版本;然后配置好环境变量,让构建可移植;接着接入 Jenkins 或 GitHub Actions,实现自动化;最后用并行任务把构建速度提上去。每一步都踩过坑,也都有对应的解决方案。

好了,这一章的内容就到这里。记住,CI/CD 不是一蹴而就的,它需要持续迭代。先把基础打牢,再逐步优化。你的构建流水线会越来越顺的。


公众号:蓝海资料掘金营,微信deep3321