服务端5月27日 23:32
Gradle 如何实现构建变体和多环境配置?构建变体是 Android Gradle 构建系统的核心机制,面试中经常围绕 buildTypes、productFlavors、flavorDimensions 和 sourceSet 合并优先级展开。理解它的关键在于:构建变体 = 构建类型 × 产品风味,是多维度组合的结果。
## 构建变体是什么
构建变体(Build Variant)是 buildTypes 和 productFlavors 的交叉组合产物。每增加一个构建类型或产品风味,变体数量都会成倍增长。假设配置了 2 个 buildTypes 和 3 个 productFlavors,最终会生成 2 × 3 = 6 个构建变体。
变体的命名规则是:产品风味名 + 构建类型名(驼峰拼接),例如 `freeDebug`、`paidRelease`。
## buildTypes 和 productFlavors 各负责什么
**buildTypes** 定义构建的行为特征——是否混淆、是否可调试、签名配置等。它回答"这个包怎么构建"。
**productFlavors** 定义产品的业务差异——包名、应用名、API 地址、功能模块等。它回答"这个包是什么版本"。
两者职责不同,组合使用才能覆盖"多版本 × 多环境"的完整需求。
## flavorDimensions 的作用和顺序
当存在多个产品风味维度时,必须用 `flavorDimensions` 声明维度名称和顺序:
```groovy
android {
flavorDimensions "version", "environment"
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
}
paid {
dimension "version"
applicationIdSuffix ".paid"
}
dev {
dimension "environment"
buildConfigField "String", "API_URL", "\"https://dev.api.example.com\""
}
staging {
dimension "environment"
buildConfigField "String", "API_URL", "\"https://staging.api.example.com\""
}
prod {
dimension "environment"
buildConfigField "String", "API_URL", "\"https://api.example.com\""
}
}
}
```
维度顺序决定 sourceSet 合并优先级——排在前面的维度优先级更高。上面的配置中 "version" 优先于 "environment",意味着 `src/free/` 中的资源会覆盖 `src/dev/` 中的同名资源。
这个配置会生成 2 × 3 × 2 = 12 个变体:freeDevDebug、freeDevRelease、freeStagingDebug ... paidProdRelease。
## sourceSet 合并优先级规则
Gradle 合并资源时按以下优先级从高到低查找:
1. `src/freeDevDebug/` — 构建变体源集(最具体,优先级最高)
2. `src/debug/` — 构建类型源集
3. `src/free/` — 产品风味源集(高优先级维度)
4. `src/dev/` — 产品风味源集(低优先级维度)
5. `src/main/` — 主源集(最低优先级)
优先级高的源集中的同名资源会覆盖优先级低的。需要注意:不同源集中不能存在同名的 Java/Kotlin 类,否则构建报错;但资源文件(strings、drawables)可以覆盖。
## 变体过滤:剔除无意义组合
多维度组合容易产生不需要的变体,用 `variantFilter` 过滤:
```groovy
android {
variantFilter { variant ->
def names = variant.flavors*.name
// 不需要 paid + dev 的组合
if (names.contains("paid") && names.contains("dev")) {
variant.setIgnore(true)
}
}
}
```
过滤后,paidDevDebug 和 paidDevRelease 不会出现在构建变体列表中,减少干扰。
## 多环境配置的常见写法
实际项目中,多环境通常这样组织:
```groovy
android {
buildTypes {
debug {
applicationIdSuffix ".debug"
debuggable true
minifyEnabled false
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
flavorDimensions "environment"
productFlavors {
dev {
dimension "environment"
applicationIdSuffix ".dev"
resValue "string", "app_name", "MyApp-Dev"
buildConfigField "String", "BASE_URL", "\"https://dev.example.com/api\""
manifestPlaceholders = [ENV_NAME: "dev"]
}
staging {
dimension "environment"
applicationIdSuffix ".staging"
resValue "string", "app_name", "MyApp-Staging"
buildConfigField "String", "BASE_URL", "\"https://staging.example.com/api\""
manifestPlaceholders = [ENV_NAME: "staging"]
}
prod {
dimension "environment"
resValue "string", "app_name", "MyApp"
buildConfigField "String", "BASE_URL", "\"https://api.example.com\""
manifestPlaceholders = [ENV_NAME: "prod"]
}
}
}
```
关键配置项说明:
- `applicationIdSuffix`:让不同环境可共存在同一设备上
- `resValue`:在构建时生成资源,代码中通过 `R.string.app_name` 引用
- `buildConfigField`:在 BuildConfig 中生成常量,代码中通过 `BuildConfig.BASE_URL` 引用
- `manifestPlaceholders`:向 AndroidManifest.xml 注入占位符变量,常用于配置不同环境的 meta-data
## 变体特定依赖
Gradle 支持按变体维度声明依赖:
```groovy
dependencies {
// 构建类型维度
debugImplementation 'com.facebook.stetho:stetho:1.6.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
// 产品风味维度
devImplementation 'com.example:dev-tools:1.0.0'
prodImplementation 'com.example:prod-analytics:1.0.0'
// 构建变体维度(最细粒度)
freeDevImplementation 'com.example:free-dev-helper:1.0.0'
}
```
依赖粒度从粗到细:implementation → buildTypeImplementation → flavorImplementation → variantImplementation。只在需要的变体中引入依赖,可以减小无用包体积。
## 常见踩坑
**变体爆炸**:3 个 flavorDimensions 各 3 个风味 + 2 个 buildTypes = 54 个变体。务必用 `variantFilter` 过滤无用组合,否则构建时间变长、AS 变体选择器卡顿。
**源集类冲突**:`src/free/` 和 `src/paid/` 中不能有同名 Java 类。如果需要不同实现,要么放在各自的源集中,要么用接口 + 工厂模式在 main 中统一调度。
**资源覆盖陷阱**:高优先级源集中的 `strings.xml` 不是整个文件覆盖,实际上资源合并是条目级别的,同名 key 会被覆盖,不同 key 会合并。但 Java/Kotlin 类不行,同包同类名直接报错。
**manifestPlaceholders 未生效**:确保在 productFlavors 中赋值时用的是 `=` 而非闭包语法,且 AndroidManifest.xml 中用 `${ENV_NAME}` 引用。标签
Gradle
Gradle 是一个强大的构建自动化系统,用于多语言软件开发,它综合了 Apache Ant 的灵活性和 Apache Maven 的生命周期管理能力。Gradle 最初于 2007 年推出,使用 Groovy(后来也支持 Kotlin)作为其领域特定语言(DSL),以编写脚本来定义项目配置和构建逻辑。

服务端5月27日 23:30
Gradle 有哪些常用命令?构建速度慢怎么优化?Gradle 是 Android 和 Java 生态的主流构建工具,掌握它的常用命令和构建优化手段是开发者的基本功,也是面试高频考点。
## 最常用的构建命令
日常开发中,这几个命令用得最多:
```bash
# 编译并打包项目
./gradlew build
# 清理上次构建产物后重新构建
./gradlew clean build
# 跳过测试加快构建
./gradlew build -x test
# 只构建 Debug 变体(Android 项目)
./gradlew assembleDebug
# 只构建 Release 变体
./gradlew assembleRelease
```
`build` 会执行编译、测试、打包全流程。如果只想产出 APK/AAB,用 `assembleDebug` 或 `assembleRelease` 更快,因为跳过了测试和校验步骤。
## 依赖查看与分析
依赖冲突是 Gradle 项目最常见的坑,这几个命令能帮你定位问题:
```bash
# 查看项目完整依赖树
./gradlew dependencies
# 只看某个配置的依赖(如 implementation)
./gradlew dependencies --configuration implementation
# 查看特定模块的依赖
./gradlew :app:dependencies
# 深入分析某个依赖的来源
./gradlew dependencyInsight --dependency gson
```
`dependencyInsight` 比 `dependencies` 更实用——它直接告诉你某个库是从哪条路径引入的,在排查版本冲突时效率很高。
## 任务查看与调试
```bash
# 列出所有可用任务
./gradlew tasks
# 包括隐藏任务在内全部列出
./gradlew tasks --all
# 预览任务执行顺序但不真正执行
./gradlew build --dry-run
# 强制重新执行所有任务(忽略缓存)
./gradlew build --rerun-tasks
# 查看项目结构
./gradlew projects
# 查看项目属性
./gradlew properties
```
`--dry-run` 在排查任务依赖关系时很有用,能看到哪些任务会被触发但不会真的执行。
## 构建优化:为什么构建这么慢?
Gradle 构建慢,根本原因通常有三个:配置阶段重复执行、任务没有利用缓存、多模块没有并行。下面逐个解决。
### 1. 启用构建缓存
构建缓存让 Gradle 跳过输入未变化的任务,直接复用上次的输出:
```bash
# 命令行临时启用
./gradlew build --build-cache
```
永久生效,在 `gradle.properties` 中配置:
```properties
org.gradle.caching=true
```
原理:每个任务根据输入内容的哈希值生成缓存键,输入没变就直接取缓存结果。修改一行代码不会导致整个项目重新编译。
### 2. 启用并行执行
多模块项目默认串行构建,开启并行后独立模块可以同时编译:
```properties
# gradle.properties
org.gradle.parallel=true
# 控制最大并行线程数(默认等于 CPU 核心数)
org.gradle.workers.max=4
```
命令行方式:
```bash
./gradlew build --parallel --max-workers=4
```
注意:只有模块间没有依赖关系的任务才能并行。如果你的模块是线性的依赖链,并行效果有限。
### 3. 启用配置缓存
这是 Gradle 8.x 之后最重要的优化。正常每次构建都要执行配置阶段(解析 build.gradle),配置缓存可以在构建脚本没变时直接跳过这一步:
```bash
# 首次尝试启用(会报告不兼容的地方)
./gradlew build --configuration-cache
```
永久配置:
```properties
org.gradle.configuration-cache=true
```
迁移阶段建议先用 `warn` 模式:
```properties
org.gradle.configuration-cache=warn
```
这样构建不会中断,但会在日志里提示哪些代码需要修改才能兼容。Gradle 9.0 已将配置缓存作为默认行为。
### 4. 启用按需配置
只配置当前任务涉及的模块,跳过无关模块的配置阶段:
```properties
org.gradle.configureondemand=true
```
对大型多模块项目(10+ 模块)效果显著,小项目差别不大。
### 5. 调整 JVM 内存
Gradle 本身跑在 JVM 上,默认内存可能不够:
```properties
# gradle.properties
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseParallelGC
```
- `-Xmx4096m`:堆内存,大型项目建议 4GB 以上
- `-XX:MaxMetaspaceSize=1024m`:类元数据空间,避免 Metaspace OOM
- `-XX:+UseParallelGC`:并行垃圾回收,降低 GC 暂停
### 6. 利用 Gradle Daemon
Daemon 是常驻后台的 Gradle 进程,避免每次构建都启动新 JVM:
```bash
# Daemon 默认已启用,确认状态
./gradlew --status
# 停止所有 Daemon(出问题时重启)
./gradlew --stop
```
Daemon 默认就是开启的,不需要额外配置。如果构建行为异常,先试试 `--stop` 重启 Daemon。
## 诊断构建瓶颈
优化之前先定位瓶颈在哪里:
```bash
# 生成构建性能报告(HTML)
./gradlew build --profile
# 生成更详细的 Build Scan(上传到 Gradle 服务器)
./gradlew build --scan
```
`--profile` 会在 `build/reports/profile/` 下生成 HTML 报告,按耗时排列各阶段和任务,一眼就能看出哪个任务最耗时。
`--scan` 生成更全面的 Build Scan,包含依赖解析时间、缓存命中率和配置阶段耗时,适合深度排查。
## 一份推荐的 gradle.properties 配置
把上面的优化汇总成一份配置,直接复制到项目根目录的 `gradle.properties`:
```properties
# 并行构建
org.gradle.parallel=true
# 构建缓存
org.gradle.caching=true
# 配置缓存(迁移阶段用 warn)
org.gradle.configuration-cache=true
# 按需配置
org.gradle.configureondemand=true
# JVM 内存和 GC 优化
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseParallelGC
# 非 Android 项目可选:开启文件系统监视(加速增量构建)
org.gradle.vfs.watch=true
```
对于 Android 项目,还可以在 `build.gradle` 中开启以下优化:
```groovy
android {
// 只构建当前变体,跳过其他变体
variants.all { variant ->
if (variant.name != 'debug') {
variant.ignore = true
}
}
}
```
## 常见构建问题与排查命令
```bash
# 依赖冲突:查看某个库的所有引入路径
./gradlew dependencyInsight --dependency 'com.google.code.gson:gson'
# 构建失败:查看完整堆栈
./gradlew build --stacktrace
# 更详细的调试信息
./gradlew build --info
# 强制刷新依赖(解决缓存损坏)
./gradlew build --refresh-dependencies
# 离线构建(只用本地缓存,不访问远程仓库)
./gradlew build --offline
# 持续构建模式(文件变化后自动重新构建)
./gradlew build --continuous
```
## 面试追问方向
- **配置缓存和构建缓存有什么区别?** 配置缓存跳过配置阶段(解析 build.gradle),构建缓存跳过执行阶段(任务的输入输出哈希匹配)。两者作用在不同阶段,互不冲突,可以同时开启。
- **并行执行有什么限制?** 只有不存在依赖关系的任务才能并行。如果模块 A 依赖模块 B,B 必须先完成。串行依赖链越长,并行收益越低。
- **Gradle Daemon 会不会导致内存泄漏?** 长时间运行的 Daemon 确实可能积累内存,Gradle 会在闲置 3 小时后自动停止 Daemon。如果遇到问题,手动 `./gradlew --stop` 即可。服务端5月27日 23:21
Gradle 是什么?它有哪些核心概念和优势?Gradle 是一个基于 JVM 的构建自动化工具,融合了 Ant 的灵活性与 Maven 的约定优于配置理念,使用 Groovy 或 Kotlin DSL 编写构建脚本。它的核心概念围绕 Project、Task、Plugin 三层展开。
## Project
每个 Gradle 构建由一个或多个 Project 组成。单模块项目只有一个 Project,多模块项目在 settings.gradle.kts 中声明子项目。每个 Project 对应一个 build.gradle.kts 脚本,是配置的顶级容器。
## Task
Task 是 Gradle 执行的最小单元,如编译、测试、打包。Task 之间通过 dependsOn 声明依赖关系,Gradle 据此生成有向无环图(DAG),按拓扑排序执行:
```kotlin
tasks.register("copyFiles") {
dependsOn("processResources")
doLast {
copy {
from("build/resources")
into("dist")
}
}
}
```
关键点:Task 分为配置阶段和执行阶段,doFirst / doLast 中的逻辑只在执行阶段运行。
## Plugin
Plugin 是功能扩展的核心机制。二进制插件通过 plugin ID 应用:
```kotlin
plugins {
id("org.springframework.boot") version "3.2.0"
}
```
插件内部通过注册 Task、扩展 DSL、配置依赖来改变构建行为。Java Plugin、Android Plugin 都是这个模式。
## 构建生命周期
Gradle 构建分三个阶段:
1. **初始化**:执行 settings.gradle.kts,确定参与构建的 Project
2. **配置**:执行所有 build.gradle.kts,构建 Task 依赖图
3. **执行**:按 DAG 顺序执行目标 Task 及其依赖
理解生命周期是排查构建问题的关键——配置阶段的代码每次构建都会执行,即使只运行一个 Task。
## 依赖管理
Gradle 支持 Maven、Ivy 仓库和本地文件依赖,提供 resolutionStrategy 处理版本冲突,默认取最新版本。implementation 与 api 的区别是常见追问点:implementation 依赖不传递,api 依赖会传递到上层模块。
## Gradle Wrapper
项目自带 gradlew 脚本,自动下载指定版本的 Gradle,保证团队构建环境一致。无需手动安装 Gradle 即可构建项目。
## 核心优势
- **增量构建**:只重新执行输入输出有变化的 Task
- **构建缓存**:跨项目复用相同 Task 的输出
- **并行执行**:多模块并行构建,利用多核 CPU
这三项机制叠加,使 Gradle 在大型项目中的构建速度远超 Maven。
## 常见追问
**Gradle 和 Maven 的核心区别?** Maven 基于 XML 声明式配置,逻辑固定在插件中;Gradle 用 DSL 编程式定义构建逻辑,灵活度更高,且增量构建和缓存机制使其速度更快。
**implementation 和 api 有什么区别?** implementation 的依赖对上层模块不可见,修改时不触发上层重新编译;api 的依赖会传递,适合暴露给消费者的公共 API 依赖。服务端5月27日 23:20
Gradle 中的 Task 是什么?如何创建和配置 Task?## 核心答案
Task 是 Gradle 构建的最小执行单元,代表一个原子操作(编译、打包、测试等)。每个 Task 包含名称、类型、动作(Action)、依赖关系和输入/输出。创建 Task 有三种方式,推荐使用 `tasks.register()` 实现延迟初始化,避免配置阶段不必要的开销。
## 创建 Task
### register vs create vs task 关键字
```groovy
// 推荐:延迟创建,仅在实际需要时实例化
tasks.register('myTask') {
doLast { println 'Executing myTask' }
}
// 立即创建,配置阶段就会实例化
tasks.create('myTask') {
doLast { println 'Executing myTask' }
}
// DSL 快捷方式,等同 create
task myTask {
doLast { println 'Executing myTask' }
}
```
`register` 与 `create` 的核心区别在于:`register` 返回 TaskProvider,Task 实例在首次被引用时才创建。大型项目中大量使用 `create` 会导致配置阶段耗时增加,`register` 可显著改善构建性能。
### 指定 Task 类型
```groovy
tasks.register('copyFiles', Copy) {
from 'src/main/resources'
into 'build/resources'
}
```
继承内置类型(Copy、Delete、Exec 等)可以复用已有的文件操作逻辑,不必从零实现。
## 配置 Task
```groovy
tasks.register('myTask') {
group = 'Custom Tasks' // Gradle 面板中的分组
description = '自定义任务描述' // gradle tasks 时的说明
dependsOn 'clean', 'compileJava' // 依赖其他任务
mustRunAfter 'test' // 硬排序约束
shouldRunAfter 'build' // 软排序约束(可被 Gradle 忽略)
inputs.file('config.properties') // 声明输入
outputs.dir('build/output') // 声明输出
doFirst { println '执行前' }
doLast { println '执行后' }
}
```
声明 inputs/outputs 的意义在于启用增量构建:Gradle 检测到输入输出未变化时会跳过 Task 执行,这是提升构建速度的关键机制。
## 依赖与排序
- **dependsOn**:声明必须先完成的依赖,Gradle 据此构建有向无环图(DAG)决定执行顺序
- **mustRunAfter**:硬约束,两个任务同时被调度时保证先后顺序,但不引入依赖
- **shouldRunAfter**:软约束,Gradle 在并行执行优化时可以忽略
- **finalizedBy**:无论当前任务成功或失败,都会执行收尾任务(如资源清理)
```groovy
tasks.register('taskA') {
finalizedBy 'cleanup'
doLast { println 'Task A' }
}
tasks.register('cleanup') {
doLast { println 'Cleanup' }
}
```
## 执行控制
```groovy
// 条件执行
tasks.register('conditionalTask') {
onlyIf { project.hasProperty('runConditional') }
doLast { println '条件满足才执行' }
}
// 禁用任务
tasks.register('disabledTask') {
enabled = false
}
// 抛出 StopExecutionException 跳过当前动作
doFirst {
if (!project.hasProperty('force')) {
throw new StopExecutionException()
}
}
```
## 追问
**register 创建的 Task 如何在其他地方引用?** 使用 `tasks.named('myTask')` 获取 TaskProvider,再通过 `.configure { }` 追加配置。不要对 register 的 Task 直接用 `tasks.myTask`,那会触发立即实例化,失去延迟初始化的优势。
**mustRunAfter 和 dependsOn 有什么区别?** dependsOn 会建立真正的依赖关系,被依赖的任务一定会执行;mustRunAfter 只是排序约束,仅当两个任务都在本次构建的执行计划中时才生效,不会把对方拉入执行图。
**为什么不要在 Task 体中直接写逻辑而要用 doFirst/doLast?** Task 闭包中的代码在配置阶段执行,而 doFirst/doLast 中的代码在执行阶段执行。配置阶段写逻辑会导致每次运行任何 Task 都会执行这些代码,产生副作用和性能浪费。服务端5月27日 23:19
Gradle 的增量构建是如何工作的?## 核心机制
Gradle 增量构建通过跟踪任务的输入(inputs)和输出(outputs)的哈希值来决定是否跳过执行。当输入未变化且输出存在时,任务标记为 UP-TO-DATE 并跳过;只有输入变化或输出缺失时才重新执行。
关键点:
- 首次构建记录所有输入输出的快照(哈希值)
- 后续构建比较当前快照与历史快照
- 增量任务可进一步获取文件级变更细节(新增/修改/删除),只处理变化部分
## 输入输出声明
没有正确声明输入输出的任务无法参与增量构建:
```groovy
abstract class ProcessTask extends DefaultTask {
@InputDirectory
abstract DirectoryProperty getInputDir()
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@TaskAction
void execute(InputChanges changes) {
if (changes.incremental) {
changes.getFileChanges(inputDir).each { change ->
// change.changeType: ADDED / MODIFIED / REMOVED
// 只处理变化的文件
}
} else {
// 全量处理
}
}
}
```
运行时 API 方式(无需自定义 Task 类):
```groovy
tasks.register('processFiles') {
inputs.dir('src/main/resources')
outputs.dir('build/processed')
doLast { /* ... */ }
}
```
## 构建性能优化手段
**1. 启用增量编译与构建缓存**
```properties
# gradle.properties
org.gradle.caching=true
org.gradle.parallel=true
```
**2. 远程构建缓存(CI 环境)**
```groovy
buildCache {
remote(HttpBuildCache) {
url = 'https://cache.example.com/cache/'
push = System.getenv('CI') != null
}
}
```
**3. 配置缓存(Configuration Cache)**:缓存项目配置阶段结果,避免每次构建重新解析 build.gradle,Gradle 8.x 已趋于稳定。
**4. 避免配置阶段耗时操作**:将逻辑移入 `doLast` / `doFirst`,而非任务配置闭包中。
## 常见增量构建失效的原因
- 输入输出未声明或声明不全 → 任务每次都执行
- 输出被外部进程修改 → 缓存校验失败
- 任务存在非确定性输出(如含时间戳) → 同样输入产生不同输出
- `outputs.upToDateWhen` 中包含非确定性逻辑
调试命令:
```bash
./gradlew build --info # 查看 UP-TO-DATE 原因
./gradlew build --rerun-tasks # 强制全量执行
./gradlew build --scan # 生成构建分析报告
```
## 追问
**Q: 增量构建和构建缓存有什么区别?**
增量构建在同一项目内比较输入输出快照跳过任务;构建缓存可以跨项目、跨机器复用任务输出,存储在本地或远程。两者互补:增量构建避免本地重复执行,构建缓存避免跨环境重复执行。
**Q: 输入归一化(Input Normalization)是什么?**
Gradle 比较输入时会对 classpath 做归一化处理——忽略 JAR 中的时间戳和顺序差异,只比较实际内容。可通过 `@Classpath` 和 `@CompileClasspath` 注解控制归一化策略,减少不必要的任务重新执行。
**Q: 配置缓存对增量构建有什么影响?**
配置缓存缓存的是配置阶段的结果(任务图、属性值),增量构建缓存的是执行阶段的输入输出快照。两者作用于不同阶段,同时启用效果叠加。但配置缓存要求构建脚本无副作用,部分插件可能不兼容。服务端5月27日 23:18
Gradle 插件有哪些类型?如何创建和使用自定义插件?## 核心回答
Gradle 插件分两类:**脚本插件**和**二进制插件**。创建自定义插件有三种方式:在 build script 中直接编写、在 buildSrc 模块中组织、以及创建独立项目发布。
## 插件类型
**脚本插件**是额外的 `.gradle` 文件,通过 `apply from` 引入:
```groovy
apply from: 'gradle/checkstyle.gradle'
```
只能在本项目复用,不推荐用于复杂逻辑。
**二进制插件**实现了 `Plugin<Project>` 接口,打包为 JAR 后可跨项目共享:
```groovy
plugins {
id 'java' // 核心插件,无需版本
id 'org.springframework.boot' version '3.0.0' // 社区插件,需指定版本
}
```
推荐使用 `plugins` DSL 而非旧式 `apply plugin:`。
## 创建自定义插件的三种方式
### 1. Build Script 内联
直接在 `build.gradle` 中编写,适合一次性逻辑:
```groovy
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.tasks.register('greet') {
doLast { println 'Hello from plugin' }
}
}
}
apply plugin: GreetingPlugin
```
无法在其他项目复用。
### 2. buildSrc 模块
在项目根目录创建 `buildSrc/src/main/groovy/` 目录,插件类放于此。整个项目可见,可编写测试,但不能跨项目共享。
### 3. 独立项目(推荐)
创建独立的 Gradle 插件项目,发布到 Maven 仓库供任意项目使用:
```groovy
// 插件项目 build.gradle
plugins {
id 'java-gradle-plugin'
id 'maven-publish'
}
gradlePlugin {
plugins {
customPlugin {
id = 'com.example.custom-plugin'
implementationClass = 'com.example.CustomPlugin'
}
}
}
```
插件实现类:
```groovy
package com.example
class CustomPlugin implements Plugin<Project> {
void apply(Project project) {
def ext = project.extensions.create('customConfig', CustomExtension)
project.tasks.register('customTask') {
group = 'Custom'
doLast { println "Config: ${ext.name}" }
}
}
}
class CustomExtension {
String name = 'default'
}
```
注册插件 ID(`META-INF/gradle-plugins/com.example.custom-plugin.properties`):
```properties
implementation-class=com.example.CustomPlugin
```
使用方引入:
```groovy
plugins {
id 'com.example.custom-plugin'
}
customConfig {
name = 'MyApp'
}
```
## 追问
- **plugins DSL 与 apply 有什么区别?** plugins DSL 在配置阶段解析,支持类型安全和版本管理;apply 在脚本执行时才应用,无法提前校验。
- **自定义插件如何测试?** 使用 Gradle TestKit 编写功能测试,在临时项目中运行构建验证行为。
- **为什么推荐用 Java/Kotlin 而非 Groovy 实现插件?** 静态类型减少二进制不兼容风险,Kotlin DSL 还能获得 IDE 补全支持。
服务端5月27日 23:17
Gradle 如何实现多项目构建?项目间依赖怎么配置?Gradle 通过 `settings.gradle` 声明子项目,通过 `project()` 依赖建立项目间引用,配合 `allprojects`/`subprojects` 共享配置,实现多项目构建。
## 核心机制
多项目构建由一个根项目和若干子项目组成。`settings.gradle` 定义了哪些子项目参与构建,Gradle 据此建立项目间的依赖图并决定构建顺序。
```groovy
// settings.gradle
rootProject.name = 'my-project'
include 'app', 'library', 'common'
// 嵌套项目
include ':data:repository'
project(':data:repository').projectDir = new File(rootDir, 'modules/data/repository')
```
## 项目间依赖配置
子项目之间通过 `project()` 函数声明依赖,Gradle 自动保证被依赖项目先构建:
```groovy
// app/build.gradle
dependencies {
implementation project(':library')
implementation project(':common')
// 指定配置
implementation project(path: ':library', configuration: 'runtimeClasspath')
}
// library/build.gradle
dependencies {
api project(':common') // api 会传递依赖给上层
}
```
`implementation` 与 `api` 的区别在多项目中尤为关键:`api` 暴露依赖给消费方,`implementation` 则不传递。
## 共享配置的三种方式
**1. allprojects / subprojects 注入**
```groovy
// 根 build.gradle
allprojects {
group = 'com.example'
version = '1.0.0'
repositories { mavenCentral() }
}
subprojects {
apply plugin: 'java'
java {
sourceCompatibility = JavaVersion.VERSION_17
}
}
```
**2. 约定插件(Convention Plugin)**
在 `buildSrc` 或独立插件项目中定义,比 `subprojects` 更灵活、可测试:
```groovy
// buildSrc/src/main/groovy/my-java-convention.gradle
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
}
// 子项目使用
// library/build.gradle
plugins { id 'my-java-convention' }
```
**3. 版本目录(Version Catalog)**
```toml
# gradle/libs.versions.toml
[versions]
spring-boot = "3.0.0"
[libraries]
spring-boot-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
```
```groovy
// 子项目中使用
dependencies {
implementation libs.spring.boot.web
}
```
## 构建执行
```bash
./gradlew projects # 查看项目结构
./gradlew :app:build # 构建指定项目(自动构建其依赖项)
./gradlew build --parallel # 并行构建
```
并行构建需要在 `gradle.properties` 中启用:
```properties
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
```
## 循环依赖问题
项目 A 依赖 B,B 又依赖 A,Gradle 构建时会报错。解决方式是抽取公共部分到第三个模块,或者用 `compileOnly`/`testImplementation` 打断传递链。可通过 `./gradlew :app:dependencies --configuration runtimeClasspath` 排查依赖链路。
## 追问
- `allprojects` 和 `subprojects` 有什么区别?——`allprojects` 包含根项目,`subprojects` 只作用于子项目。
- 约定插件相比 `subprojects` 闭包有什么优势?——可复用、可测试、支持按需引入,避免全局污染。
- 如何让子项目独立构建而不触发其他项目?——`configureondemand` 配合按需配置,Gradle 6+ 默认部分启用。服务端5月27日 23:16
Gradle 依赖管理有哪些配置类型?如何解决冲突?Gradle 通过 configurations(依赖配置)和 repositories(仓库)两大机制管理依赖。configurations 定义依赖的可见范围和生命周期,repositories 定义依赖的获取来源。
## 核心依赖配置类型
| 配置 | 编译时 | 运行时 | 传递给消费者 | 典型场景 |
|------|--------|--------|--------------|----------|
| implementation | 可见 | 可见 | 不可见 | 默认选择,大多数依赖用这个 |
| api | 可见 | 可见 | 可见 | 库模块需要暴露给下游的 API 依赖 |
| compileOnly | 可见 | 不可见 | 不可见 | Lombok 等仅编译期需要的依赖 |
| runtimeOnly | 不可见 | 可见 | 不可见 | JDBC 驱动等仅运行时需要的依赖 |
| annotationProcessor | 可见 | 不可见 | 不可见 | 注解处理器 |
```groovy
dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
api 'org.apache.commons:commons-math3:3.6.1' // 消费者也能访问
compileOnly 'org.projectlombok:lombok:1.18.24' // 编译后丢弃
runtimeOnly 'mysql:mysql-connector-java:8.0.28' // 运行时才加载
annotationProcessor 'org.projectlombok:lombok:1.18.24'
}
```
**implementation 与 api 的关键区别:** implementation 的依赖不会出现在消费者的编译类路径中,改动后只重新编译当前模块;api 会传递,改动后消费者也要重新编译。优先用 implementation 可以显著缩短构建时间。
## 版本管理
推荐使用版本目录(Version Catalog),在 `gradle/libs.versions.toml` 中集中声明:
```toml
[versions]
spring-boot = "3.0.0"
[libraries]
spring-boot-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
```
```groovy
dependencies {
implementation libs.spring.boot.web
}
```
多模块项目也可用 BOM 统一版本:
```groovy
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.0.0')
implementation 'org.springframework.boot:spring-boot-starter-web' // 版本由 BOM 控制
}
```
## 依赖冲突与排除
Gradle 默认取最高版本。当冲突不可自动解决时,可强制版本或排除:
```groovy
// 排除特定传递依赖
implementation('org.springframework.boot:spring-boot-starter-web:3.0.0') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
// 强制版本
configurations.all {
resolutionStrategy {
force 'org.apache.commons:commons-lang3:3.12.0'
}
}
```
查看依赖树排查冲突:`./gradlew dependencies --configuration implementation`
## 追问
**Q: 为什么 implementation 改了不会触发下游模块重编译?**
A: implementation 的依赖被隔离在当前模块的编译类路径内,消费者编译时看不到这些依赖,因此改动不影响消费者的编译产物。这是 Gradle 3.4 引入 implementation 替代 compile 的核心动机。
**Q: 什么时候必须用 api 而不能用 implementation?**
A: 当模块的公开 API 方法签名中用到了某个依赖的类型时(如返回值或参数类型来自该依赖),必须用 api,否则消费者编译时会找不到类。
**Q: 动态版本(如 `3.+`)有什么问题?**
A: 构建不可复现——同一份代码不同时间构建可能拉到不同版本,导致线上行为不一致。生产环境必须锁定版本。服务端5月27日 23:16
Gradle 的生命周期包括哪些阶段?## 三个阶段:初始化 → 配置 → 执行
Gradle 构建生命周期分为 **初始化(Initialization)**、**配置(Configuration)**、**执行(Execution)** 三个阶段,依次完成项目确定、任务图构建和任务执行。
## 初始化阶段
读取 `settings.gradle`,确定参与构建的项目并为每个项目创建 `Project` 实例:
```groovy
// settings.gradle
rootProject.name = 'my-app'
include 'app', 'core', 'utils'
```
钩子:`gradle.projectsLoaded`
## 配置阶段
执行所有项目的 `build.gradle`,配置插件、依赖和任务,建立任务依赖图。**无论最终执行哪个任务,所有项目的构建脚本都会被执行**——这是性能问题的常见来源。
```groovy
// 延迟注册任务,避免配置阶段不必要的开销
tasks.register('myTask') {
onlyIf { project.hasProperty('enableMyTask') }
}
```
钩子:`gradle.beforeProject` / `gradle.afterProject`
## 执行阶段
按依赖图顺序执行目标任务及其依赖。支持增量构建(仅处理变更文件)和并行执行(`--parallel`)。
钩子:`gradle.taskGraph.whenReady` / `gradle.taskGraph.beforeTask` / `gradle.taskGraph.afterTask`
## 常见追问
**Q: 为什么配置阶段会成为性能瓶颈?**
每个项目的 `build.gradle` 都会执行,即使只跑一个任务。大量 I/O 或复杂逻辑放在配置阶段会拖慢整个构建。应使用 `tasks.register()` 延迟创建、启用 `--configuration-cache` 缓存配置结果。
**Q: `afterEvaluate` 的作用?**
在项目配置完成后回调,常用于在插件配置完成后再添加依赖或修改属性,确保顺序正确。
**Q: 增量构建的原理?**
Gradle 为每个 Task 维护输入输出的哈希,若输入输出均未变化则跳过执行(标记 `UP-TO-DATE`),而非真正重新运行。服务端2月21日 19:11
Gradle 和 Maven 有什么区别?如何选择?Gradle 与 Maven 是两个最流行的 Java 构建工具,它们各有优缺点。以下是两者的详细对比:
## 历史背景
### Maven
- 发布时间:2004年
- 开发者:Apache Software Foundation
- 设计理念:约定优于配置(Convention over Configuration)
- 配置文件:XML(pom.xml)
### Gradle
- 发布时间:2008年
- 开发者:Gradle Inc.
- 设计理念:结合 Ant 的灵活性和 Maven 的约定
- 配置文件:Groovy/Kotlin DSL(build.gradle)
## 核心差异对比
### 1. 配置方式
#### Maven (XML)
```xml
<!-- pom.xml -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
#### Gradle (Groovy DSL)
```groovy
// build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.0'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0'
}
```
### 2. 构建性能
#### Maven
- **首次构建**:较慢,需要下载依赖
- **增量构建**:基本支持,但不如 Gradle 高效
- **并行构建**:支持,但配置复杂
- **构建缓存**:有限支持
#### Gradle
- **首次构建**:较快,增量构建优化
- **增量构建**:非常高效,只处理变更的文件
- **并行构建**:原生支持,配置简单
- **构建缓存**:强大的本地和远程缓存支持
### 3. 依赖管理
#### Maven
```xml
<dependencies>
<!-- 固定版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.0</version>
</dependency>
<!-- 使用属性 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- 依赖管理(BOM) -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</dependencies>
```
#### Gradle
```groovy
dependencies {
// 固定版本
implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0'
// 使用版本目录(推荐)
implementation libs.spring.boot.web
// 使用 BOM
implementation platform('org.springframework.boot:spring-boot-dependencies:3.0.0')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
```
### 4. 生命周期
#### Maven
```bash
# Maven 生命周期
clean
validate
compile
test
package
verify
install
deploy
# 执行命令
mvn clean install
mvn clean package
```
#### Gradle
```bash
# Gradle 任务(无固定生命周期)
clean
build
test
jar
install
publish
# 执行命令
./gradlew clean build
./gradlew clean jar
```
### 5. 插件系统
#### Maven
```xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
```
#### Gradle
```groovy
plugins {
// 二进制插件
id 'java'
id 'org.springframework.boot' version '3.0.0'
// 脚本插件
apply from: 'gradle/checkstyle.gradle'
}
// 插件配置
java {
sourceCompatibility = JavaVersion.VERSION_17
}
springBoot {
mainClass = 'com.example.Application'
}
```
## 优缺点对比
### Maven 优点
1. **标准化**:严格遵循约定,项目结构统一
2. **稳定性**:成熟稳定,生态系统完善
3. **学习曲线**:相对简单,易于上手
4. **IDE 支持**:所有主流 IDE 都有良好支持
5. **文档**:文档丰富,社区活跃
### Maven 缺点
1. **灵活性差**:XML 配置冗长,难以自定义
2. **构建速度**:大型项目构建速度较慢
3. **依赖管理**:版本冲突处理不够灵活
4. **扩展性**:插件开发相对复杂
### Gradle 优点
1. **灵活性高**:Groovy/Kotlin DSL 表达力强
2. **构建速度快**:增量构建和缓存机制优秀
3. **依赖管理**:灵活的依赖解析和版本管理
4. **多语言支持**:支持 Java、Kotlin、Groovy、Scala 等
5. **可扩展性**:插件开发简单,易于扩展
### Gradle 缺点
1. **学习曲线**:需要学习 Groovy/Kotlin DSL
2. **复杂性**:灵活性可能导致配置复杂
3. **标准化**:不如 Maven 严格,可能导致项目结构不一致
4. **文档**:虽然文档丰富,但相对分散
## 适用场景
### 适合使用 Maven 的场景
1. **传统企业项目**:需要严格遵循标准
2. **简单项目**:不需要复杂的构建逻辑
3. **团队熟悉度**:团队已经熟悉 Maven
4. **CI/CD 集成**:需要与现有 Maven 生态系统集成
5. **依赖管理简单**:不需要复杂的依赖处理
### 适合使用 Gradle 的场景
1. **大型项目**:需要高效的增量构建
2. **复杂构建逻辑**:需要自定义构建流程
3. **多模块项目**:需要灵活的模块间依赖
4. **Android 开发**:Android Studio 默认使用 Gradle
5. **性能要求高**:需要快速构建和缓存
## 迁移指南
### 从 Maven 迁移到 Gradle
```bash
# 使用 Gradle 的 Maven 插件生成初始配置
./gradlew init --type pom
# 或使用在线工具
# https://start.spring.io/ 可以生成 Gradle 项目
```
### 从 Gradle 迁移到 Maven
```bash
# 使用 Maven 的 Gradle 插件
mvn com.github.ekryd.sortpom:sortpom-maven-plugin:3.3.0:sort
```
## 性能对比数据
### 构建时间对比(基于相同项目)
| 操作 | Maven | Gradle | 差异 |
|------|-------|--------|------|
| 首次构建 | 120s | 90s | Gradle 快 25% |
| 增量构建(无变更) | 45s | 5s | Gradle 快 89% |
| 增量构建(少量变更) | 60s | 15s | Gradle 快 75% |
| 清理构建 | 30s | 20s | Gradle 快 33% |
## 最佳实践建议
### 选择 Maven 如果
- 项目结构简单,不需要复杂构建逻辑
- 团队已经熟悉 Maven
- 需要与现有 Maven 生态系统集成
- 重视标准化和一致性
### 选择 Gradle 如果
- 项目规模大,需要高性能构建
- 需要自定义构建逻辑
- 团队愿意学习新技术
- 需要支持多种语言或平台
## 结论
Maven 和 Gradle 都是优秀的构建工具,选择哪一个取决于项目需求和团队情况:
- **Maven**:适合传统、标准化、简单的项目
- **Gradle**:适合现代、高性能、复杂的项目
两者可以共存,Gradle 可以导入 Maven 项目,Maven 也可以使用 Gradle 插件。选择时应该基于实际需求,而不是盲目追求新技术。