使用 GitHub Actions 为 Java/Maven 项目配置 CI
1. 概述
持续集成(CI)帮助你在推送代码时立即发现错误,而不是等到合并时才“搞砸了”。本文将指导你为 Java/Maven 项目在 GitHub Actions 上构建 CI 流程:构建、运行测试、缓存依赖、使用 build matrix 测试多个 JDK 版本以及发布报告。读完后,你可以将工作流文件复制到实际项目中,几分钟内就能运行。
2. 准备示例项目
假设你有一个简单的 Spring Boot 项目,使用标准 Maven 结构。最少的 pom.xml 文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nonladev</groupId>
<artifactId>order-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
依赖项 junit-jupiter 和 assertj-core 用于编写测试。插件 maven-surefire-plugin 负责在调用 mvn test 时运行测试。
一个小的业务类用于测试:
package com.nonladev.order;
public class OrderCalculator {
public double calculateTotal(double unitPrice, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("数量必须大于 0");
}
return unitPrice * quantity;
}
}
相应的测试:
package com.nonladev.order;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class OrderCalculatorTest {
private final OrderCalculator calculator = new OrderCalculator();
@Test
void shouldCalculateTotalCorrectly() {
double total = calculator.calculateTotal(50_000, 3);
assertThat(total).isEqualTo(150_000);
}
@Test
void shouldThrowWhenQuantityIsInvalid() {
assertThatThrownBy(() -> calculator.calculateTotal(50_000, 0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("数量必须大于 0");
}
}
shouldCalculateTotalCorrectly 验证求和是否正确。shouldThrowWhenQuantityIsInvalid 验证当 quantity 无效时是否抛出异常。这是 CI 每次你推送代码时都会运行的测试。
3. 基础 CI 工作流
GitHub Actions 读取 .github/workflows/ 目录中的 YAML 文件。创建文件 .github/workflows/ci.yml:
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build and run tests
run: mvn -B clean verify
逐部分解释:
on: 工作流在push或pull_request针对main分支时运行。actions/checkout@v4: 将源代码克隆到运行器中。actions/setup-java@v4: 安装 Temurin 版本的 JDK 21 — 在调用mvn之前总是需要这一步。mvn -B clean verify: 标志-B(批处理模式)禁用交互式输出,适合 CI 环境。clean verify从头构建并运行测试(verify阶段包括test)。
推送提交,进入 GitHub 上的 Actions 标签,你会看到任务运行和类似的日志:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.nonladev.order.OrderCalculatorTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
如果测试失败,任务会显示红色状态,你会在日志中立即看到堆栈跟踪 — 无需 SSH 到任何机器。
4. 缓存 Maven 依赖以加快构建速度
每次任务在新的运行器(临时性)上运行时,Maven 都必须从 .m2/repository 重新下载所有依赖项。对于 Spring Boot 项目,这每次可能需要 1-2 分钟。setup-java 内置支持缓存:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
属性 cache: 'maven' 自动基于 pom.xml 文件的哈希值缓存 ~/.m2/repository 目录。下次构建时,如果 pom.xml 没有更改,Actions 会立即恢复缓存,跳过重新下载每个 jar 的步骤。
你可以通过查看添加缓存前后“构建和运行测试”步骤的时间来检查效果 — 通常减少 30-60%。
注意: 如果项目使用 Maven Wrapper(
./mvnw),应该根据mvnw进行缓存,而不是依赖运行器上预装的 Maven 版本,因为 GitHub 每次更新镜像时,默认 Maven 版本可能会改变。
5. 使用 build matrix 测试多个 JDK 版本
如果你维护需要兼容多个 Java 版本的库,使用 strategy.matrix 并行运行多个任务:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [ '17', '21' ]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: 'maven'
- name: Build and run tests
run: mvn -B clean verify
matrix.java-version 列出要测试的版本列表。GitHub Actions 自动创建 2 个并行运行的任务:一个使用 JDK 17,一个使用 JDK 21。变量 ${{ matrix.java-version }} 被替换到每个相应的任务中。
Actions 标签上的结果显示 2 个单独的任务,例如 build (17) 和 build (21),帮助你立即了解项目在哪个 Java 版本失败。
6. 发布测试报告和检查覆盖率
默认情况下,测试日志只在 Actions 中显示为文本形式。为了获得更容易阅读的报告,使用 dorny/test-reporter action:
- name: Build and run tests
run: mvn -B clean verify
- name: Publish test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: '**/surefire-reports/*.xml'
reporter: java-junit
if: success() || failure():此步骤在测试失败时也会运行(但在任务被取消时不会运行),以便你始终看到报告。path 指向 Surefire 在运行测试后生成的 XML 文件。报告以 PR 中的单独检查形式出现,列出每个测试用例的通过/失败情况。
如果要测量覆盖率,请将 JaCoCo 插件添加到 pom.xml:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
运行 mvn verify 会自动在 target/site/jacoco/index.html 生成 HTML 报告。你可以将其上传为工件:
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-report
path: target/site/jacoco/
此工件可以从工作流运行摘要页面下载,可以直接在浏览器中查看。
7. 构建状态徽章
工作流运行稳定后,将徽章添加到 README.md 中,以便人们无需进入 Actions 就能了解 CI 状态:

用实际的 GitHub 名称替换 <org>/<repo>。徽章根据默认分支上最近一次运行的结果自动更新为绿色/红色。
8. 常见错误
- 忘记设置
java-version与maven.compiler.source匹配:本地构建通过但 CI 失败,错误为release version X not supported。始终在pom.xml和工作流之间同步 JDK 版本。 - 使用
mvn test而不是mvn verify:如果项目在verify阶段有集成测试运行(例如 Failsafe 插件),mvn test会跳过它们,造成虚假的安全感。 - 更改依赖项时缓存密钥错误:如果你自己编写缓存操作(
actions/cache)但忘记将pom.xml包含在密钥哈希中,旧缓存可能导致构建使用过时的依赖项。使用setup-java的cache: 'maven'来避免这种情况。 - 在没有 Docker/网络稳定的自托管运行器上运行 CI:Maven 需要从中央存储库下载依赖项;如果运行器被网络阻止,构建会在下载步骤卡住。检查代理或使用内部 Maven 镜像。
注意: 如果你的项目很小,只有几个简单的测试,并且不经常部署,一个包含 matrix 和覆盖报告的完整 CI 工作流可能会过度设计。从第 3 节的基本工作流开始,然后随着项目增长逐步扩展。
9. 结论
只需几十行 YAML,你就有了一个自动构建、测试、缓存依赖项和发布报告的 Maven 项目 CI 流程。从简单的工作流开始,然后根据实际需求添加 JDK matrix、JaCoCo 覆盖率和 README 徽章。最重要的不是工作流的复杂性,而是 CI 在你每次推送代码时都能稳定可靠地运行。