本章目标
本章重点从"能看懂 Gradle"升级到"能维护真实项目构建"。学完后你应该能:
- 创建和维护单模块、多模块 Gradle 工程。
- 正确使用
implementation、api、runtimeOnly、testImplementation。 - 处理依赖冲突、版本统一、BOM、排除、替换。
- 理解 Fat Jar、Manifest、sourceSets 扩展。
- 配置测试覆盖率收集和测试过滤。
- 理解 Java、Kotlin、Spring Boot、Android 项目中 Gradle 配置的共同点。
1. 单模块项目构建
最小 Java 应用通常需要两个插件:
groovy
plugins {
id 'java'
id 'application'
}
java 负责编译、测试、打包,application 负责定义入口类并提供 run task。
groovy
application {
mainClass = 'com.example.Main'
}
常用命令:
bash
gradle clean # 清理构建输出
gradle compileJava # 仅编译
gradle test # 执行测试
gradle run # 运行应用
gradle build # 完整构建(编译+测试+打包)
gradle jar # 生成 jar(不含依赖)
gradle installDist # 生成可分发包
gradle distZip # 生成 zip 分发包
2. 标准源码目录与 sourceSets 扩展
标准目录
Gradle Java 插件默认使用 Maven 风格目录:
text
src/main/java 生产 Java 代码
src/main/resources 生产资源
src/test/java 测试 Java 代码
src/test/resources 测试资源
修改默认源码目录
如果项目目录不标准,也可以显式配置:
groovy
sourceSets {
main {
java {
srcDirs = ['source/java']
}
resources {
srcDirs = ['source/resources']
}
}
}
新增额外源码集(SourceSet)
新增一个 integration 测试集(集成测试和单元测试分开):
groovy
sourceSets {
integration {
java.srcDir 'src/integration/java'
resources.srcDir 'src/integration/resources'
compileClasspath += sourceSets.main.output + configurations.testRuntimeClasspath
runtimeClasspath += output + compileClasspath
}
}
tasks.register('integrationTest', Test) {
description = 'Runs integration tests.'
group = 'verification'
testClassesDirs = sourceSets.integration.output.classesDirs
classpath = sourceSets.integration.runtimeClasspath
useJUnitPlatform()
shouldRunAfter test
}
check.dependsOn integrationTest
这样单元测试和集成测试可以分开执行:
bash
gradle test # 只跑单元测试
gradle integrationTest # 只跑集成测试
gradle check # 全部跑
3. 多模块项目
多模块项目通过根目录 settings.gradle 注册模块:
groovy
rootProject.name = 'company-platform'
include 'app', 'service', 'common'
也可以嵌套模块:
groovy
include 'app'
include 'service:user-service'
include 'service:order-service'
include 'common'
典型依赖关系:
text
app -> service -> common
模块依赖写法:
groovy
dependencies {
implementation project(':service')
}
4. implementation 和 api 的区别
implementation 不把依赖暴露给下游模块,能减少编译污染并提升增量编译效率。
api 会把依赖暴露给下游模块,适合公共接口签名中出现的类型。
使用 api 需要 java-library 插件:
groovy
plugins {
id 'java-library'
}
示例:
java
// common 模块
public interface JsonCodec {
com.fasterxml.jackson.databind.JsonNode parse(String text);
}
因为接口方法签名暴露了 Jackson 类型,所以 Jackson 应该用 api。如果 Jackson 只在方法内部使用,则应该用 implementation。
判断规则:
| 场景 | 推荐依赖范围 |
|---|---|
| 依赖类型出现在 public API 中 | api |
| 只在模块内部实现使用 | implementation |
| 只在测试中使用 | testImplementation |
| 运行时才需要(驱动、日志实现) | runtimeOnly |
| 编译需要但不打包(注解、API Stub) | compileOnly |
5. 依赖版本统一
直接声明版本
小项目可以直接在 dependencies 中写版本:
groovy
implementation 'com.google.guava:guava:33.2.1-jre'
Version Catalog(推荐)
多模块项目建议使用 Version Catalog(gradle/libs.versions.toml):
toml
[versions]
guava = "33.2.1-jre"
junit = "5.10.2"
jackson = "2.17.1"
mockito = "5.11.0"
[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
[plugins]
spring-boot = { id = "org.springframework.boot", version = "3.3.0" }
使用:
groovy
dependencies {
implementation libs.guava
implementation libs.jackson.databind
testImplementation libs.junit.jupiter
testImplementation libs.mockito.core
}
好处:
- 版本集中管理。
- 多模块不会各写各的版本。
- 升级依赖时更容易评估影响面。
- IDE 可以跳转到版本定义。
BOM(Bill of Materials)平台依赖
BOM 是一个只声明版本的 POM,引入后可以不写具体版本:
groovy
dependencies {
// 引入 Spring Boot BOM,版本由 BOM 统一管理
implementation platform('org.springframework.boot:spring-boot-dependencies:3.3.0')
// 不需要写版本号,由 BOM 决定
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
自定义平台模块(类似 BOM):
groovy
// platform 模块的 build.gradle
plugins {
id 'java-platform'
}
javaPlatform {
allowDependencies()
}
dependencies {
api platform('org.springframework.boot:spring-boot-dependencies:3.3.0')
constraints {
api 'com.google.guava:guava:33.2.1-jre'
api 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
}
}
其他模块引用:
groovy
dependencies {
implementation platform(project(':platform'))
implementation 'org.springframework.boot:spring-boot-starter-web'
}
6. 依赖冲突排查
查看依赖树:
bash
gradle :app:dependencies --configuration runtimeClasspath
定位某个依赖为什么被引入:
bash
gradle :app:dependencyInsight --dependency guava --configuration runtimeClasspath
排除传递依赖
groovy
dependencies {
implementation('org.example:library:1.0') {
exclude group: 'com.google.guava', module: 'guava'
}
}
全局排除(所有配置):
groovy
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'
}
强制版本(force)
groovy
configurations.configureEach {
resolutionStrategy {
force 'com.google.guava:guava:33.2.1-jre'
}
}
依赖替换(substitution)
将某个依赖替换为另一个(常用于本地 stub 或组件迁移):
groovy
configurations.configureEach {
resolutionStrategy.dependencySubstitution {
// 用本地模块替换外部依赖(本地联调时有用)
substitute module('com.example:common') using project(':common')
// 替换坐标(组件改名时)
substitute module('javax.servlet:servlet-api') using module('jakarta.servlet:jakarta.servlet-api:6.0.0')
}
}
依赖约束(推荐方式)
groovy
dependencies {
constraints {
implementation('com.google.guava:guava:33.2.1-jre') {
because '统一 Guava 版本,避免传递依赖引入旧版本'
}
}
}
7. 测试配置
JUnit 5 基础配置
groovy
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
test {
useJUnitPlatform()
}
显示测试日志
groovy
test {
useJUnitPlatform()
testLogging {
events 'passed', 'skipped', 'failed'
showStandardStreams = false
exceptionFormat = 'full'
}
}
参数化测试示例(Java)
java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"10, -5, 5"
})
void add(int a, int b, int expected) {
assertEquals(expected, a + b);
}
}
测试过滤
bash
# 执行单模块测试
gradle :service:test
# 执行单个测试类
gradle :service:test --tests "com.example.UserServiceTest"
# 执行单个测试方法
gradle :service:test --tests "com.example.UserServiceTest.shouldReturnUserById"
# 执行名称匹配的所有测试
gradle test --tests "*Service*"
测试超时控制
groovy
test {
useJUnitPlatform()
timeout = Duration.ofMinutes(5) // 单个测试套件超时
}
8. 打包和 Fat Jar
标准 Jar(不含依赖)
bash
gradle :app:jar
生成在 app/build/libs/app-1.0.0.jar,不含依赖,通常需要配合 classpath 运行。
配置 Manifest
groovy
jar {
manifest {
attributes(
'Main-Class': 'com.example.Main',
'Implementation-Title': project.name,
'Implementation-Version': project.version,
'Built-By': System.getProperty('user.name'),
'Build-Jdk': System.getProperty('java.version')
)
}
}
Fat Jar(手写,不用插件)
把所有依赖打进一个 jar:
groovy
tasks.register('fatJar', Jar) {
group = 'build'
description = 'Build a fat jar containing all runtime dependencies'
archiveClassifier = 'fat'
manifest {
attributes 'Main-Class': 'com.example.gradledemo.app.App'
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
执行:
bash
gradle fatJar
java -jar app/build/libs/app-1.0.0-fat.jar
可分发包(Application 插件)
bash
gradle installDist # 生成可运行目录(含 bin/ 和 lib/)
gradle distZip # 生成 zip 包
gradle distTar # 生成 tar 包
输出:
text
app/build/install/app/
├── bin/
│ ├── app # Unix 启动脚本
│ └── app.bat # Windows 启动脚本
└── lib/
├── app-1.0.0.jar
└── *.jar # 所有依赖
9. Spring Boot 项目构建要点
Spring Boot 常见配置:
groovy
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
常用 task:
bash
gradle bootRun # 直接运行应用
gradle bootJar # 生成可执行 fat jar
gradle bootBuildImage # 构建 Docker 镜像(需要 Docker)
gradle dependencies # 查看所有依赖
要点:
bootJar生成可执行 jar,包含所有依赖。jar是普通 jar,Spring Boot 插件会禁用它(可配置恢复)。io.spring.dependency-management导入 Spring BOM,无需写版本。
多环境配置
groovy
bootRun {
args = ['--spring.profiles.active=dev']
jvmArgs = ['-Xmx512m']
}
10. Kotlin 项目构建
Kotlin JVM 项目构建:
groovy
plugins {
id 'org.jetbrains.kotlin.jvm' version '2.0.0'
}
kotlin {
jvmToolchain(17)
}
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
}
test {
useJUnitPlatform()
}
Kotlin DSL 写法(build.gradle.kts):
kotlin
plugins {
kotlin("jvm") version "2.0.0"
}
kotlin {
jvmToolchain(17)
}
dependencies {
implementation(kotlin("stdlib"))
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testImplementation(kotlin("test-junit5"))
}
tasks.test {
useJUnitPlatform()
}
Kotlin + Spring Boot:
groovy
plugins {
id 'org.jetbrains.kotlin.jvm' version '2.0.0'
id 'org.jetbrains.kotlin.plugin.spring' version '2.0.0'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
11. Android Gradle 项目要点
Android 项目通常使用 Android Gradle Plugin:
groovy
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android' version '2.0.0'
}
核心配置:
groovy
android {
namespace 'com.example.app'
compileSdk 35
defaultConfig {
applicationId 'com.example.app'
minSdk 23
targetSdk 35
versionCode 1
versionName '1.0'
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
}
Android 项目和 Java 项目的共同点是:仍然由插件创建 task、由依赖配置管理三方库、由 Gradle 负责构建图和执行。
12. 实操:给 demo 验证模块依赖关系
目标:理解 app -> service -> common 的模块关系。
步骤:
bash
cd demo/gradle-multi-module-demo
gradle :app:dependencies --configuration runtimeClasspath
验证点:
- 能看到
project :service。 - 能看到
project :common。 - 能看到外部依赖
guava。
运行:
bash
gradle :app:run
预期输出包含:
text
Gradle Demo | <当前日期>
Hello, Gradle Learner. Multi-module build is working.
依赖 insight 查询:
bash
gradle :app:dependencyInsight --dependency guava --configuration runtimeClasspath
验证点:
- 能看到
guava是通过:common传递进来的。 - 能看到版本号
33.2.1-jre。
13. 常见问题
问题 1:多模块依赖写错导致找不到类
先检查 settings.gradle 是否 include 了模块,再检查依赖方向是否正确。
错误示例:
groovy
implementation project('service')
正确示例:
groovy
implementation project(':service')
问题 2:为什么 api 用多了会变慢
api 会扩大模块对外暴露的编译 classpath。公共 API 变化时,下游模块更容易被迫重新编译。默认优先使用 implementation,只有类型出现在公共签名中才使用 api。
问题 3:为什么本地能构建,CI 失败
常见原因:
- 本地依赖了缓存,CI 是干净环境。
- JDK 版本不一致。
- 本地用了未提交的文件。
- 没有使用 Wrapper 固定 Gradle 版本。
- 私服或代理配置只在本机存在。
排查顺序:
bash
gradle clean build --refresh-dependencies
gradle --version
git status
问题 4:BOM 和 Version Catalog 能同时用吗
可以。BOM 管理来自外部框架(如 Spring)的版本,Version Catalog 管理你自己声明的其他依赖版本,两者互不冲突。
问题 5:Fat Jar 打包时报 duplicate files 错误
设置去重策略:
groovy
tasks.register('fatJar', Jar) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
// ...
}
或者选择保留 META-INF/services(服务加载所需):
groovy
duplicatesStrategy = DuplicatesStrategy.INCLUDE // 保留所有(可能导致冲突)
更好的做法是使用 Shadow 插件(com.github.johnrengelman.shadow),它能正确合并 META-INF/services。