Gradle 的项目结构与源码集(Source Sets)详解(Kotlin DSL)

![97a3f839d3c24b068f46b44097e253ba

作为 Java 开发者,大概率对 Maven + Java 项目的目录结构 烂熟于心:开发时将业务代码放在 src/main/java、配置文件塞到 src/main/resources,测试代码和测试资源则对应放进 src/test/javasrc/test/resources------ 这套目录结构作为一种"约定",使得开发人员无需额外配置,Maven 就能自动识别源码位置、处理资源文件,,完成编译打包。

当我们使用 Gradle 构建项目(尤其是 SpringBoot 项目)时,会发现这种熟悉感丝毫未减,因为 Gradle 深度兼容了 Maven 的目录约定,遵循 "约定优于配置" 原则,即便不写一行脚本,只要按上述目录存放代码和资源,Gradle 就能自动执行 compileJava(编译生产代码)、compileTestJava(编译测试代码)等核心任务。而支撑这一切 "无感知兼容" 的核心机制,正是 sourceSets(源码集)------ 它就像 Gradle 背后的 "约定执行者",默默将目录规则与构建流程绑定。

本文将从 Gradle 项目的标准结构 入手,逐步拆解源码集的概念、默认配置与自定义实战,帮你既掌握 "约定" 的便捷性,又能灵活应对复杂项目的结构设计需求。

💡 不同类型的项目(如 Java、Kotlin、Android)目录结构略有差异,本文以 Java 项目 为例

一、Gradle Java 项目的标准结构

1.1 单模块项目结构

一个典型的 Gradle 单模块项目(以 Java 应用为例)的目录集中在 src 下,所有 Java 源代码、测试代码及资源文件按类型(生产 / 测试)进行划分,比如 src/main/java 对应生产源码、src/test/resources 对应测试资源,典型结构如下:

plaintext 复制代码
gradle-project/
├── build.gradle.kts   		// 模块构建脚本(配置依赖、任务、源码集等)
├── settings.gradle.kts 	// 项目设置脚本(指定项目名称、包含的子模块)
└── src/
    ├── main/          		// 生产环境核心代码(必选目录)
    │   ├── java/      		// Java 源代码(业务逻辑、工具类等)
    │   └── resources/ 		// 生产资源文件(application.yml、静态资源等)
    │
    └── test/          		// 测试代码(可选目录)
        ├── java/      		// 测试 Java 源码(单元测试、测试工具类等)
        └── resources/ 		// 测试资源文件(测试用配置、模拟数据等)

1.2 多模块项目的结构

当项目规模扩大时,通常会拆分为多模块(核心层 + 接口层 + 公共层),通过「父模块统一管理,子模块各司其职」降低耦合。典型多模块结构(以 SpringBoot 项目为例)如下:

plaintext 复制代码
gradle-project/
├── build.gradle.kts       // 父模块构建脚本(统一管理子模块依赖版本、公共插件)
├── settings.gradle.kts    // 项目设置脚本(声明子模块:common、core、web)
├── common/                // 子模块 1:公共工具模块(封装跨模块工具类、常量)
│   ├── build.gradle.kts   // 子模块专属配置(如引入工具类依赖)
│   └── src/               // 子模块源码(遵循单模块目录约定)
│       ├── main/
│       └── test/
├── core/                  // 子模块 2:核心业务模块(处理业务逻辑、数据访问)
│   ├── build.gradle.kts
│   └── src/
└── api/                   // 子模块 3:api 接口模块
    ├── build.gradle.kts
    └── src/

💡 多模块项目中,父模块的 src 目录通常不使用------ 所有 Java 源代码和资源由子模块各自维护,父模块仅负责全局配置(如依赖版本锁定、插件统一引入),避免子模块配置重复。

二、什么是 Source Sets?

在 Gradle 中,SourceSets 是对项目 "源码分组" 的抽象描述。它不仅定义了 "源码 / 资源该放在哪里",还关联了 "该分组需要哪些依赖""编译后输出到哪里""对应的构建任务是什么"。

简单来说,Source Sets(源码集) 本质是「一组源代码目录 + 资源目录 + 关联任务」的集合。其核心思想是通过「目录」区分项目中不同类型的代码(如生产代码与测试代码),实现代码的隔离与复用;同时自动关联对应的编译、测试、打包等构建流程。

每个 SourceSet 包含以下关键组成部分,共同支撑对源码的全生命周期管理:

组成部分 说明
源代码目录 存放源代码文件,例如 src/main/javasrc/test/java
资源目录 存放非代码资源,如 src/main/resources(生产资源)、src/test/resources(测试资源)。
类路径 编译或运行时依赖的 JAR 包、库文件。
关联任务 Gradle 自动创建的构建任务,如 compileJava(编译 Java 代码)、processResources(处理资源文件)、test(运行测试用例)等。

2.1 默认源码集

当项目引入 Java 插件(或基于其扩展的插件,如 java-libraryapplication)后,Gradle 会自动创建 maintest 源码集:

源码集名称 用途 依赖关系 默认目录
main 生产环境核心代码 无(其他源码集依赖它) src/main/javasrc/main/resources
test 单元测试 / 集成测试代码 依赖 main 源码集 src/test/javasrc/test/resources
2.1.2 main 源码集
目录结构

main 源码集用于存放项目的核心业务代码和生产环境资源,包含 Java 源代码和资源文件两部分:

plaintext 复制代码
src/
└── main/
    ├── java/         # Java 源代码(核心业务逻辑)
    └── resources/    # 生产环境资源(配置文件、静态资源等)
目录 用途
src/main/java/ 存放 Java 源代码(核心业务逻辑代码)
src/main/resources/ 存放生产环境相关的资源文件(如配置文件、静态资源等)
关联任务

Gradle 会为 main 源码集自动生成编译源码处理资源 的关联任务,在执行 build 等顶层构建命令时自动触发:

  • compileJava 任务 :负责编译 src/main/java 目录下的所有 Java 源代码,输出到 build/classes/java/main 目录。
  • processResources 任务 :处理(复制 / 过滤) src/main/resources 目录下的资源文件(如配置文件、静态资源等)到 build/resources/main 目录。

当执行 build 时,Gradle 会优先触发 processResourcescompileJava任务:

  1. compileJava 完成后,build/classes/java/main 中会生成所有生产代码的 .class 文件;

  2. processResources 完成后,build/resources/main 中会生成可直接用于运行的资源文件;

最终这两个任务的输出(build/classes/java/mainbuild/resources/main)会作为输入,用于后续的 jar(打包)、assemble(聚合产物)等任务,最终生成可交付的 JAR/WAR 包。

2.2.2 test 源码集
目录结构

test 源码集用于存放验证 main 源码集功能的测试代码(如单元测试、组件测试),包含测试源代码和测试资源文件两部分:

plaintext 复制代码
src/
└── test/
    ├── java/         # 测试源代码(基于 JUnit、TestNG 等框架)
    └── resources/    # 测试资源(测试配置、测试数据等)
目录 用途
src/test/java/ 存放测试代码(单元测试、集成测试等),通常基于 JUnit、TestNG 框架。
src/test/resources/ 存放测试资源文件(测试配置、测试数据等),运行测试时会自动加载。
关联任务

Gradle 会自动为 test 源码集生成以下核心任务支撑完整的测试执行流程:

  • compileTestJava:编译 src/test/java 目录下的测试源代码 .java 文件转换为 .class 字节码,输出到 build/classes/java/test 目录。
  • processTestResources:处理 src/test/resources 目录下的测试资源文件,最终输出到 build/resources/test 目录。

执行 test 任务时,Gradle 会先执行 compileTestJavaprocessTestResources,确保测试代码和资源准备就绪,再通过测试框架(如 JUnit)执行测试用例,最终输出测试结果(成功 / 失败数量、耗时等)。

2.2 自定义任务查看 Source Sets

  1. build.gradle.kts 构建脚本中引入 java 插件:

    kotlin 复制代码
    plugins {
        java
    }
  2. build.gradle.kts创建自定义sourceSets任务,打印项目所有的 Source Sets 信息:

    kotlin 复制代码
    tasks.register("sourceSets") {
        doLast {
            // 获取所有的 sourceSets
            sourceSets.forEach { sourceSet ->
                println("Source Set: ${sourceSet.name}")
    
                // 输出 Java 源码目录的相对路径
                sourceSet.java.srcDirs.forEach {
                    println("  Java sources: ${project.relativePath(it)}")
                }
    
                // 输出资源目录的相对路径
                sourceSet.resources.srcDirs.forEach {
                    println("  Resources: ${project.relativePath(it)}")
                }
            }
        }
    }
  3. 在项目根目录执行任务:

    bash 复制代码
    gradle sourceSets
  4. 控制台输出以下:

三、自定义 integrationTest 源码集

默认的 maintest 足以满足简单项目需求,但对于复杂项目(如需要区分单元测试和集成测试),自定义源码集是更优选择,自定义源码集需结合「目录创建」「依赖配置」「任务绑定」三步。

集成测试 为例(与单元测试隔离,通常需要启动外部服务或数据库),我们将创建 integrationTest 源码集,实现代码隔离和独立执行。

3.1 创建目录结构

遵循 Gradle 源码集的约定优于配置原则,在 src 目录下创建 integrationTest 源码集的目录结构:

复制代码
src/
├── integrationTest/	# 集成测试源码集
│   ├── java/         	# 集成测试 Java 代码
│   └── resources/    	# 集成测试资源(配置文件等)
├── main/
│   └── ...
└── test/
    └── ...

3.2 配置源码集

build.gradle.kts 中通过 sourceSets 块添加 integrationTest 源码集配置,指定 integrationTest 源码集的目录路径和类路径,确保集成测试能访问业务代码和必要的依赖:

groovy 复制代码
sourceSets {
    // 定义名为 integrationTest 的源码集
    create("integrationTest") {
        // 配置源码目录
        java {
            srcDir("src/integrationTest/java") // Java 源码路径
        }
        resources {
            srcDir("src/integrationTest/resources") // 资源路径
        }

        // 依赖 main 源码集的编译结果(即可以直接调用业务代码)
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output

        // 复用 test 源码集的依赖(如 JUnit 测试框架)
        compileClasspath += sourceSets.test.get().output
        runtimeClasspath += sourceSets.test.get().output
    }
}

刷新 Gradle 后,IDE(如 IntelliJ IDEA)会自动识别为测试源码目录,无需额外配置:

3.3 关联任务

定义 integrationTest 源码集后,Gradle 会自动生成一系列任务用于处理源码编译、资源拷贝、清理等基础工作,命名遵循如下规则:

plaintext 复制代码
[固定前缀] + [源码集名称(驼峰命名)] + [固定后缀]
  • 固定前缀 :表示任务的 "核心动作",由任务类型决定(如 compile 代表编译,process 代表资源处理)。
  • 源码集名称 (驼峰命名):关联的自定义源码集名称(如 integrationTest ),与源码集一一对应。
  • 固定后缀 :表示任务的 "功能范围"(如 Java 代表编译 Java 代码,Resources 代表处理资源文件)。

结合 "自定义源码集 integrationTest" 为例:

任务类型 命名规则 示例(源码集:integrationTest)
编译任务 compile + [SourceSetName] + Java compileIntegrationTestJava
资源处理任务 process + [SourceSetName] + Resources processIntegrationTestResources
聚合任务 [SourceSetName] + Classes integrationTestClasses

3.4 添加依赖

集成测试需要测试框架(如 JUnit),需要配置依赖:

groovy 复制代码
dependencies {
    // 集成测试依赖 JUnit
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
}

3.5 创建集成测试任务

需要注册一个 Test 类型的任务,关联 integrationTest 源码集的编译结果,指定测试类路径和运行时路径,并配置测试执行规则(如框架、依赖顺序、过滤规则):

kotlin 复制代码
// 创建 integrationTest 任务
tasks.register<Test>("integrationTest") {
    description = "执行集成测试"
    group = "Verification" // 归类到「验证」分组(与 test 任务同组)
    
    // 关联 integrationTest 源码集的编译结果和类路径
    testClassesDirs = sourceSets["integrationTest"].output.classesDirs
    classpath = sourceSets["integrationTest"].runtimeClasspath
    
    // 配置测试框架
    useJUnitPlatform()
    
    // 过滤测试类(仅执行以 IntegrationTest 结尾的类)
    //include("**/*IntegrationTest.java")
}

3.6 执行集成测试

  1. 创建集成测试代码:src/integrationTest/java/example/NewIntegrationTest.java

    java 复制代码
    public class NewIntegrationTest {
    
        @Test
        public void test() {
            System.out.println("集成测试完成");
        }
    
    }
  2. 在项目根目录执行以下命令运行集成测试:

    bash 复制代码
    gradle integrationTest
  3. 查看控制台输出测试通过信息:

相关推荐
Android-Flutter6 小时前
kotlin - 显示heic图片
android·kotlin
Frank_HarmonyOS6 小时前
Kotlin之类委托和属性委托
kotlin
林栩link6 小时前
【车载Android】Gradle自定义插件从编写到发布
android·gradle
用户69371750013846 小时前
Android 应用崩溃前截屏-让问题排查更轻松
android
2501_938780286 小时前
Kotlin Multiplatform Mobile(KMM):实现 iOS 与 Android 共享业务逻辑
android·ios·kotlin
chilavert3186 小时前
技术演进中的开发沉思-151 java-servlet:会话管理
java·开发语言
SheepHappy6 小时前
MyBatis-Plus 源码阅读(一)CRUD 代码自动生成原理深度剖析
java
狂奔小菜鸡6 小时前
Day7 | Java的流程控制详解
java·后端·编程语言
霸道流氓气质6 小时前
Java中使用Collator实现对象List按照中文姓名属性进行A-Z的排序实现
java·开发语言·list