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 集成到并行任务优化,你可以对照着梳理自己的构建流程。
这张图把本章的四个核心模块串起来了。你从 Gradle Wrapper 开始,确保团队统一版本;然后配置好环境变量,让构建可移植;接着接入 Jenkins 或 GitHub Actions,实现自动化;最后用并行任务把构建速度提上去。每一步都踩过坑,也都有对应的解决方案。
好了,这一章的内容就到这里。记住,CI/CD 不是一蹴而就的,它需要持续迭代。先把基础打牢,再逐步优化。你的构建流水线会越来越顺的。