8、Java项目构建:Java插件详解、sourceSets配置、编译、测试、打包(jar/war)

说实话,Gradle 里最常用的场景就是构建 Java 项目。你想想看,Java 项目无非就是写源码、配资源、跑测试、最后打个包。Gradle 的 Java 插件把这些事安排得明明白白。我刚开始用 Gradle 的时候,觉得它比 Maven 啰嗦,后来才发现——嗯,这玩意儿是真灵活。

8.1 Java 插件:到底干了什么?

Java 插件是 Gradle 生态里最核心的插件之一。你只要在 build.gradle 里加上一句:

plugins {
    id 'java'
}

Gradle 就会自动帮你注册一堆任务。比如 compileJavaprocessResourcesclassesjartest 等等。说白了,它把 Java 项目的标准生命周期全给你定义好了。

我个人习惯,新建 Java 项目第一件事就是配好 Java 版本兼容性:

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

这里有个坑——sourceCompatibility 只控制源码编译的 JDK 版本,不保证你用的 API 也是这个版本。我曾经遇到过项目里用了 Java 17 的 API,但 sourceCompatibility 设的是 11,结果编译通过,运行时直接崩了。所以记得还要配 toolchain

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
注意: toolchain 配置会强制 Gradle 使用指定 JDK 来编译和运行测试,避免本地 JDK 版本不一致导致的问题。

8.2 sourceSets:源码目录的指挥官

Gradle 默认的源码目录结构是这样的:

src/
├── main/
│   ├── java/        // 生产代码
│   └── resources/   // 资源文件
└── test/
    ├── java/        // 测试代码
    └── resources/   // 测试资源

但实际项目里,经常需要自定义。比如我做过一个微服务项目,需要把 apiimplintegration-test 分开。这时候就要动 sourceSets 了:

sourceSets {
    main {
        java {
            srcDirs = ['src/main/java', 'src/api/java']
        }
        resources {
            srcDirs = ['src/main/resources', 'src/api/resources']
        }
    }
    integrationTest {
        java {
            srcDirs = ['src/integration-test/java']
        }
        resources {
            srcDirs = ['src/integration-test/resources']
        }
    }
}

你可能会问:为什么要这么折腾?直接按包名分不行吗?

其实,sourceSets 不只是目录划分,它还决定了编译顺序、依赖隔离、打包范围。比如 integrationTest 的代码可以依赖 main 的编译产物,但不会被打进生产 jar 里。这在大型项目里特别有用。

小技巧: 自定义 sourceSet 后,记得手动配置对应的 configuration,否则依赖可能拉不到。比如:
configurations {
    integrationTestImplementation.extendsFrom testImplementation
}

8.3 编译:不只是 javac

Gradle 的编译任务 compileJava 默认调用 javac,但你可以加很多调料。比如开启增量编译:

tasks.withType(JavaCompile).configureEach {
    options.incremental = true
    options.encoding = 'UTF-8'
    options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']
}

我个人建议,一定要开 -Xlint 警告。很多隐蔽的 bug 都是 unchecked 操作埋下的雷。我曾经接手过一个老项目,编译时满屏 warning,结果线上出了个 ClassCastException,查了半天才发现是泛型擦除导致的。

另外,如果你用 Lombok 或 MapStruct,记得加 annotation processor:

dependencies {
    annotationProcessor 'org.projectlombok:lombok:1.18.30'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
}

这里有个容易忽略的点——annotationProcessor 和 implementation 是两回事。Lombok 的注解处理器只在编译时生效,运行时不需要,所以别放错 scope。

8.4 测试:JUnit 5 与并行执行

测试是 Java 项目里最容易被忽视的环节。Gradle 默认用 JUnit 4,但现在是 2025 年了,我建议直接上 JUnit 5:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}

tasks.named('test') {
    useJUnitPlatform()
    maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
}

为什么要并行?因为测试多了以后,单线程跑真的很慢。我有个项目 2000 多个单元测试,串行跑要 15 分钟,并行后降到 4 分钟。不过要注意——并行测试必须保证测试之间没有共享状态,否则会随机失败。

避坑指南: 我曾经在并行测试时遇到一个诡异问题——测试 A 和测试 B 单独跑都通过,一起跑就挂。查了半天,发现是测试 A 写了一个静态变量,测试 B 读到了脏数据。解决方案:要么用 @TestInstance(Lifecycle.PER_METHOD),要么把静态变量改成 ThreadLocal。

另外,测试报告也很重要。Gradle 默认生成 HTML 报告在 build/reports/tests/test/index.html。你可以自定义输出格式:

test {
    reports {
        html.required = true
        junitXml.required = true
    }
}

8.5 打包:jar 与 war 的抉择

Java 项目最终要交付,要么是 jar,要么是 war。Gradle 的 jar 任务默认打包 main sourceSet 的编译产物和资源文件。

但默认的 jar 有个问题——它不包含依赖。如果你要打一个可执行的 fat jar,需要这样配:

jar {
    manifest {
        attributes(
            'Main-Class': 'com.example.Application'
        )
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

不过我个人更推荐用 application 插件或者 Spring Boot 的 bootJar,它们处理依赖合并更专业。比如 Spring Boot 项目:

plugins {
    id 'org.springframework.boot' version '3.2.0'
}

bootJar {
    archiveFileName = 'my-app.jar'
}

如果是 Web 项目需要打 war 包,部署到 Tomcat 等容器里:

plugins {
    id 'war'
}

war {
    archiveFileName = 'my-webapp.war'
}

这里要注意——war 包默认不包含嵌入式容器依赖。如果你用 Spring Boot 打 war,需要把 spring-boot-starter-tomcat 设为 providedRuntime

dependencies {
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}

8.6 知识体系总览

下面这张图,是我梳理的 Java 项目构建核心流程。你可以把它当作一个检查清单:

Java 项目构建核心流程 源码与资源 sourceSets 配置 编译 compileJava / 增量编译 测试 JUnit 5 / 并行执行 打包 jar / war / fat jar 跳过测试 关键配置点: • sourceSets:自定义源码目录、资源目录、测试目录 • compileJava:编码、增量编译、注解处理器、Lint 警告 • test:JUnit 平台、并行数、测试报告、静态变量隔离

8.7 总结

Java 插件是 Gradle 构建的基石。sourceSets 让你灵活组织代码,编译和测试环节帮你提前发现问题,打包则决定了交付物的形态。我个人觉得,理解这些任务之间的依赖关系,比死记硬背配置更重要

最后说一句:别怕改配置。Gradle 的 DSL 虽然啰嗦,但每行配置都有它的道理。你多试几次,自然就熟了。


公众号:蓝海资料掘金营,微信deep3321