标签

Gradle

Gradle 是一个强大的构建自动化系统,用于多语言软件开发,它综合了 Apache Ant 的灵活性和 Apache Maven 的生命周期管理能力。Gradle 最初于 2007 年推出,使用 Groovy(后来也支持 Kotlin)作为其领域特定语言(DSL),以编写脚本来定义项目配置和构建逻辑。

Gradle
查看更多相关内容
服务端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}` 引用。
服务端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 插件。选择时应该基于实际需求,而不是盲目追求新技术。