昨天用Maven把源码变成通过测试的产物。今天保留同一份Java代码,换成Gradle,观察构建规则怎样从生命周期阶段转为任务依赖图。学完这一篇,你应该能启动项目、检查测试报告、定位依赖,并把统一构建入口交给同事和CI。
本文示例固定Gradle9.3.0、JDK21,使用Kotlin DSL。构建脚本写成Kotlin语法,并不要求业务代码改成Kotlin。
一、先分清四类文件
settings.gradle.kts确定构建的项目名称和子项目;build.gradle.kts声明插件、依赖和任务配置;src保存主代码与测试代码;Wrapper文件固定实际执行的Gradle版本。
单项目目录如下:
text
build-lab/
├─ settings.gradle.kts
├─ build.gradle.kts
├─ gradlew / gradlew.bat
├─ gradle/wrapper/
└─ src/
├─ main/java/demo/App.java
└─ test/java/demo/AppTest.java
settings.gradle.kts只需:
kotlin
rootProject.name = "build-lab"
Wrapper不是把Gradle安装目录塞进项目。它由启动脚本、Wrapper JAR和属性文件组成,首次运行根据distributionUrl取得指定发行版,后续复用本机缓存。团队提交这些Wrapper文件,让开发机与CI使用同一个入口。Wrapper说明
二、完整配置与业务代码
build.gradle.kts:
kotlin
plugins { application }
repositories { mavenCentral() }
group = "demo"
version = "1.0.0"
java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } }
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test { useJUnitPlatform() }
application { mainClass.set("demo.App") }
application插件带来Java构建能力和应用运行、分发任务。repositories指定解析依赖的位置;dependencies说明库进入哪种使用场景;toolchain选择编译和测试使用的Java工具链;mainClass指定启动类。Application插件
运行Gradle的JVM与项目工具链是两个概念。 本例统一用JDK21,先用gradle --version核对启动环境。配置toolchain并不意味着任意机器都会自动下载JDK;工具链必须能被本机发现,或配置下载解析器。
App.java:
java
package demo;
public class App {
public static String label(String title) {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("title is blank");
}
return "TODO: " + title.strip();
}
public static void main(String[] args) {
System.out.println(label(args.length == 0 ? "learn Maven" : args[0]));
}
}
AppTest.java:
java
package demo;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class AppTest {
@Test void trimsTitle() {
assertEquals("TODO: learn Maven", App.label(" learn Maven "));
}
@Test void rejectsBlank() {
assertThrows(IllegalArgumentException.class, () -> App.label(" "));
}
@Test void rejectsNull() {
assertThrows(IllegalArgumentException.class, () -> App.label(null));
}
}
JUnit依赖只放入测试配置,useJUnitPlatform选择测试执行平台。显式声明launcher运行时依赖,避免把IDE提供的测试环境误当成命令行构建的一部分。
三、第一次构建与Wrapper入口
在已安装Gradle9.3.0的机器上,在项目目录执行一次:
powershell
gradle wrapper --gradle-version 9.3.0
生成后,Windows使用项目内脚本:
powershell
.\gradlew.bat --version
.\gradlew.bat clean build
.\gradlew.bat run
.\gradlew.bat installDist
Linux和macOS使用./gradlew,仓库应保留脚本的可执行权限。首次下载需要可访问发行包与依赖仓库,仓库凭据不要写进提交的构建脚本。
run直接运行配置的主类;installDist生成包含应用JAR、运行时依赖和启动脚本的目录。本例Windows入口为build/install/build-lab/bin/build-lab.bat。普通build/libs中的JAR不自动等于"包含所有依赖且能java -jar运行的胖包",交付时选清楚产物形式。
四、build依赖哪些工作
Gradle按任务依赖关系决定要做什么。 Java插件中build依赖assemble与check;assemble负责组装产物,check负责验证,test是验证链的一部分。运行某个任务,会带上它需要的前置任务,而不是执行构建脚本中所有看起来排在前面的语句。Java插件任务
因此不能把昨天Maven例子的"测试后才打包"原样套进Gradle:build同时依赖产物与检查,测试失败会让整个build失败,但不代表此前绝不可能生成JAR。流水线应只在build成功后发布制品,而不能仅凭文件存在就放行。

图:build依赖产物组装和验证,整个构建成功后再交付。
用下面的命令看任务与计划:
powershell
.\gradlew.bat tasks --all
.\gradlew.bat build --dry-run
.\gradlew.bat test --rerun-tasks
--dry-run列出计划,不执行任务动作;--rerun-tasks要求重新执行,适合确认测试确实运行。两者用途不同。
五、依赖配置与增量状态
| 配置 | 用途 |
|---|---|
| implementation | 主代码实现需要的依赖 |
| compileOnly | 只在编译时需要 |
| runtimeOnly | 仅运行时需要 |
| testImplementation | 测试编译与运行依赖 |
| testRuntimeOnly | 仅测试运行需要 |
本例主程序只使用JDK,所以没有implementation依赖。引入业务库时,先确定它在哪个classpath中需要。构建插件与项目运行库也要分开:插件负责构建行为,项目依赖供应用或测试使用。
排查某个库来自哪里:
powershell
.\gradlew.bat dependencies --configuration testRuntimeClasspath
.\gradlew.bat dependencyInsight --dependency junit-jupiter --configuration testRuntimeClasspath
任务显示UP-TO-DATE,表示输入输出检查认为无需重做;FROM-CACHE表示复用了构建缓存产物;NO-SOURCE说明没有该任务需要处理的源输入。不要把这三种状态都解释成"任务没工作"。排查缺失测试先看测试路径、名称和测试平台,再看报告,最后决定是否强制重跑。
每次都clean会删除构建产物,方便从干净状态验收,也会放弃很多增量收益。日常开发通常直接build;需要验证干净构建或排除旧产物影响时再clean。
六、用失败实验守住交付
本例验收包含正常构建、测试故意失败和恢复构建。测试报告位于build/reports/tests/test/index.html,XML结果位于build/test-results/test。本次正常构建的3个测试全部通过,run输出TODO: learn Maven。把正常输入测试的预期值改成WRONG后,3个测试中1个失败,build返回失败,但build/libs/build-lab-1.0.0.jar已经存在。恢复断言后,重新执行clean build run installDist,构建和测试再次通过。这个实验说明,制品文件存在和构建验收成功是两个不同的条件。
CI流程可按"检出代码→选择JDK→调用Wrapper build→归档测试报告→成功后发布制品"组织。固定Gradle版本解决构建工具差异,固定依赖版本和锁定策略解决依赖漂移,工具链解决Java版本差异;三个层次不能互相替代。
Maven适合希望沿用统一生命周期与约定的项目,Gradle适合需要可组合任务与构建逻辑的项目。选择依据是团队生态、插件支持、构建复杂度与维护成本,不能只比较XML和Kotlin脚本长短。
七、🧠 思维导图
#mermaid-svg-LX5v2plcewHgmDus{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LX5v2plcewHgmDus .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LX5v2plcewHgmDus .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LX5v2plcewHgmDus .error-icon{fill:#552222;}#mermaid-svg-LX5v2plcewHgmDus .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LX5v2plcewHgmDus .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LX5v2plcewHgmDus .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LX5v2plcewHgmDus .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LX5v2plcewHgmDus .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LX5v2plcewHgmDus .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LX5v2plcewHgmDus .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LX5v2plcewHgmDus .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LX5v2plcewHgmDus .marker.cross{stroke:#333333;}#mermaid-svg-LX5v2plcewHgmDus svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LX5v2plcewHgmDus p{margin:0;}#mermaid-svg-LX5v2plcewHgmDus .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LX5v2plcewHgmDus .cluster-label text{fill:#333;}#mermaid-svg-LX5v2plcewHgmDus .cluster-label span{color:#333;}#mermaid-svg-LX5v2plcewHgmDus .cluster-label span p{background-color:transparent;}#mermaid-svg-LX5v2plcewHgmDus .label text,#mermaid-svg-LX5v2plcewHgmDus span{fill:#333;color:#333;}#mermaid-svg-LX5v2plcewHgmDus .node rect,#mermaid-svg-LX5v2plcewHgmDus .node circle,#mermaid-svg-LX5v2plcewHgmDus .node ellipse,#mermaid-svg-LX5v2plcewHgmDus .node polygon,#mermaid-svg-LX5v2plcewHgmDus .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LX5v2plcewHgmDus .rough-node .label text,#mermaid-svg-LX5v2plcewHgmDus .node .label text,#mermaid-svg-LX5v2plcewHgmDus .image-shape .label,#mermaid-svg-LX5v2plcewHgmDus .icon-shape .label{text-anchor:middle;}#mermaid-svg-LX5v2plcewHgmDus .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LX5v2plcewHgmDus .rough-node .label,#mermaid-svg-LX5v2plcewHgmDus .node .label,#mermaid-svg-LX5v2plcewHgmDus .image-shape .label,#mermaid-svg-LX5v2plcewHgmDus .icon-shape .label{text-align:center;}#mermaid-svg-LX5v2plcewHgmDus .node.clickable{cursor:pointer;}#mermaid-svg-LX5v2plcewHgmDus .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LX5v2plcewHgmDus .arrowheadPath{fill:#333333;}#mermaid-svg-LX5v2plcewHgmDus .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LX5v2plcewHgmDus .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LX5v2plcewHgmDus .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LX5v2plcewHgmDus .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LX5v2plcewHgmDus .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LX5v2plcewHgmDus .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LX5v2plcewHgmDus .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LX5v2plcewHgmDus .cluster text{fill:#333;}#mermaid-svg-LX5v2plcewHgmDus .cluster span{color:#333;}#mermaid-svg-LX5v2plcewHgmDus div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LX5v2plcewHgmDus .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LX5v2plcewHgmDus rect.text{fill:none;stroke-width:0;}#mermaid-svg-LX5v2plcewHgmDus .icon-shape,#mermaid-svg-LX5v2plcewHgmDus .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LX5v2plcewHgmDus .icon-shape p,#mermaid-svg-LX5v2plcewHgmDus .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LX5v2plcewHgmDus .icon-shape .label rect,#mermaid-svg-LX5v2plcewHgmDus .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LX5v2plcewHgmDus .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LX5v2plcewHgmDus .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LX5v2plcewHgmDus :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Gradle构建
Wrapper固定版本
配置任务图
依赖配置
验收交付
build
assemble产物
check测试
implementation与testImplementation
test报告
installDist启动脚本
八、总结
总结要点
Wrapper统一入口,工具链统一Java环境。 项目把构建要求写入版本库,同事和CI才能复现同一套规则。
任务图说明真正的依赖关系。 build成功是交付条件,目录里出现JAR不能替代测试通过。
报告和依赖图比反复重跑更有用。 先识别失败任务、classpath与增量状态,再处理对应问题。
下一篇进入第25项版本控制,用Git说明工作区、暂存区、提交与分支如何协作。
👉 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!😊