Appearance
Spring Boot Gradle 插件:可执行归档文件打包详解 🚀
概述
在现代 Java 应用开发中,如何将应用程序及其所有依赖项打包成一个可独立运行的文件,是每个开发者都会遇到的重要问题。Spring Boot Gradle 插件提供了强大的可执行归档文件打包功能,让我们能够轻松创建 "Fat JAR" 或 "Fat WAR",实现 java -jar
一键启动应用。
NOTE
可执行归档文件(Executable Archives)是包含应用程序代码和所有依赖项的自包含文件,可以直接通过 java -jar
命令运行,无需额外配置类路径。
为什么需要可执行归档文件? 🤔
传统部署的痛点
在没有可执行归档文件之前,Java 应用部署面临诸多挑战:
kotlin
// 1. 需要手动管理类路径
java -cp "app.jar:lib/spring-core.jar:lib/spring-boot.jar:..." com.example.Application
// 2. 依赖项分散,容易丢失
├── app.jar
├── lib/
│ ├── spring-boot-starter-web-2.7.0.jar
│ ├── tomcat-embed-core-9.0.62.jar
│ └── ... (数十个依赖文件)
// 3. 部署复杂,环境配置繁琐
kotlin
// 一个命令搞定!
java -jar my-application.jar
// 单文件包含一切
my-application.jar (包含所有依赖)
├── BOOT-INF/
│ ├── classes/ # 应用代码
│ ├── lib/ # 所有依赖
│ └── classpath.idx # 类路径索引
├── META-INF/
│ └── MANIFEST.MF # 启动配置
└── org/springframework/boot/loader/ # Spring Boot 加载器
Spring Boot 的解决方案
Spring Boot 通过以下机制解决了传统部署的痛点:
可执行 JAR 文件打包 📦
基础配置
Spring Boot Gradle 插件会自动创建 bootJar
任务:
kotlin
// build.gradle.kts
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.20"
kotlin("plugin.spring") version "1.9.20"
}
// bootJar 任务自动创建,无需额外配置
// 运行 ./gradlew bootJar 即可生成可执行 JAR
实际应用示例
让我们创建一个简单的 Spring Boot 应用来演示:
kotlin
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@SpringBootApplication
class DemoApplication
@RestController
class HelloController {
@GetMapping("/hello")
fun hello(): String {
return "Hello from executable JAR! 🎉"
}
}
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
kotlin
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.20"
kotlin("plugin.spring") version "1.9.20"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
}
// bootJar 任务会自动配置
tasks.named<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
archiveFileName.set("my-awesome-app.jar")
}
构建和运行:
bash
# 构建可执行 JAR
./gradlew bootJar
# 运行应用
java -jar build/libs/my-awesome-app.jar
# 测试应用
curl http://localhost:8080/hello
# 输出: Hello from executable JAR! 🎉
TIP
bootJar
任务会自动包含在 assemble
和 build
任务中,所以运行 ./gradlew build
也会生成可执行 JAR。
可执行 WAR 文件打包 🌐
为什么需要可执行 WAR?
WAR 文件传统上需要部署到 Servlet 容器(如 Tomcat)中运行。Spring Boot 的可执行 WAR 既可以独立运行,也可以部署到外部容器:
kotlin
// build.gradle.kts
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.20"
kotlin("plugin.spring") version "1.9.20"
war
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
}
双重部署能力
kotlin
// 作为可执行文件运行
java -jar my-app.war
// 内嵌 Tomcat 启动
// 适合开发环境和容器化部署
kotlin
// 部署到外部 Tomcat
cp my-app.war /opt/tomcat/webapps/
// Tomcat 自动解压并部署
// 适合传统企业环境
配置示例
kotlin
// 应用主类需要继承 SpringBootServletInitializer
@SpringBootApplication
class DemoApplication : SpringBootServletInitializer() {
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
return application.sources(DemoApplication::class.java)
}
}
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
IMPORTANT
使用 providedRuntime
而不是 compileOnly
配置 Tomcat 依赖,因为 compileOnly
的依赖不会出现在测试类路径中,会导致 Web 集成测试失败。
可执行和普通归档文件的并存 🔄
默认行为
Spring Boot 插件默认会同时生成两种类型的归档文件:
bash
build/libs/
├── my-app.jar # 可执行 JAR (bootJar 任务)
└── my-app-plain.jar # 普通 JAR (jar 任务)
自定义分类器
如果你希望可执行归档文件使用分类器,而普通归档文件不使用:
kotlin
tasks.named<BootJar>("bootJar") {
archiveClassifier.set("boot")
}
tasks.named<Jar>("jar") {
archiveClassifier.set("")
}
// 结果:
// ├── my-app.jar # 普通 JAR
// └── my-app-boot.jar # 可执行 JAR
kotlin
tasks.named<Jar>("jar") {
enabled = false
}
// 只生成可执行 JAR
// ├── my-app.jar # 可执行 JAR
WARNING
在创建原生镜像时不要禁用 jar
任务,详见 Spring Boot 官方文档 #33238。
高级配置选项 ⚙️
主类配置
Spring Boot 提供多种方式配置应用主类:
kotlin
tasks.named<BootJar>("bootJar") {
mainClass.set("com.example.MyApplication")
}
kotlin
springBoot {
mainClass.set("com.example.MyApplication")
}
kotlin
plugins {
application
}
application {
mainClass.set("com.example.MyApplication")
}
kotlin
tasks.named<BootJar>("bootJar") {
manifest {
attributes("Start-Class" to "com.example.MyApplication")
}
}
NOTE
如果主类是用 Kotlin 编写的,需要使用生成的 Java 类名。例如,ExampleApplication
类会生成 ExampleApplicationKt
类。如果使用了 @JvmName
注解,则使用注解指定的名称。
开发依赖包含
默认情况下,developmentOnly
配置中的依赖不会包含在可执行归档文件中:
kotlin
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
developmentOnly("org.springframework.boot:spring-boot-devtools")
// 默认不会包含在 bootJar 中
}
// 如果需要包含开发依赖:
tasks.named<BootWar>("bootWar") {
classpath(configurations["developmentOnly"])
}
需要解压的库配置
某些库在嵌套 JAR 中无法正常工作,需要解压到临时目录:
kotlin
tasks.named<BootJar>("bootJar") {
requiresUnpack("**/jruby-complete-*.jar")
// 或使用闭包进行更精细的控制
requiresUnpack { element ->
element.name.contains("problematic-library")
}
}
完全可执行归档文件 🖥️
什么是完全可执行?
完全可执行归档文件在 JAR 文件前添加了 Shell 脚本,使其在 Unix-like 系统上可以像普通可执行文件一样运行:
kotlin
tasks.named<BootJar>("bootJar") {
launchScript()
}
使用示例
bash
# 构建完全可执行 JAR
./gradlew bootJar
# 直接运行(无需 java -jar)
./build/libs/my-app.jar
# 安装为系统服务
sudo ln -s /path/to/my-app.jar /etc/init.d/my-app
sudo service my-app start
自定义启动脚本
kotlin
tasks.named<BootJar>("bootJar") {
launchScript {
properties(mapOf(
"logFilename" to "my-app.log",
"pidFilename" to "my-app.pid",
"useStartStopDaemon" to "false"
))
}
}
kotlin
tasks.named<BootJar>("bootJar") {
launchScript {
script = file("src/main/scripts/custom-launch.script")
}
}
CAUTION
完全可执行归档文件可能与某些工具不兼容。例如,jar -xf
可能无法正确提取完全可执行的 JAR 文件。建议仅在直接执行时使用此功能。
分层 JAR 打包 🏗️
什么是分层 JAR?
分层 JAR 将应用内容按变更频率分组,优化 Docker 镜像构建的缓存效率:
默认分层配置
Spring Boot 默认定义了四个层:
kotlin
// 默认分层(按变更频率从低到高排序)
val defaultLayers = listOf(
"dependencies", // 稳定的第三方依赖
"spring-boot-loader", // Spring Boot 加载器
"snapshot-dependencies", // 快照版本依赖
"application" // 应用代码和资源
)
自定义分层配置
完整的分层配置示例
kotlin
tasks.named<BootJar>("bootJar") {
layered {
application {
// Spring Boot 加载器单独分层
intoLayer("spring-boot-loader") {
include("org/springframework/boot/loader/**")
}
// 其余应用内容
intoLayer("application")
}
dependencies {
// 项目依赖放入应用层
intoLayer("application") {
includeProjectDependencies()
}
// 快照依赖单独分层
intoLayer("snapshot-dependencies") {
include("*:*:*SNAPSHOT")
}
// 稳定依赖
intoLayer("dependencies")
}
// 定义层的顺序(重要!)
layerOrder.set(listOf(
"dependencies",
"spring-boot-loader",
"snapshot-dependencies",
"application"
))
}
}
Docker 优化示例
使用分层 JAR 优化 Docker 镜像构建:
dockerfile
FROM openjdk:17-jdk-slim
# 提取分层内容
COPY build/libs/my-app.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted
# 按层复制(利用 Docker 层缓存)
COPY extracted/dependencies/ ./ # [!code highlight]
COPY extracted/spring-boot-loader/ ./ # [!code highlight]
COPY extracted/snapshot-dependencies/ ./ # [!code highlight]
COPY extracted/application/ ./ # [!code highlight]
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
bash
# 首次构建
Step 4/8 : COPY extracted/dependencies/ ./
---> Using cache # ✅ 缓存命中
Step 5/8 : COPY extracted/spring-boot-loader/ ./
---> Using cache # ✅ 缓存命中
Step 6/8 : COPY extracted/snapshot-dependencies/ ./
---> Using cache # ✅ 缓存命中
Step 7/8 : COPY extracted/application/ ./
---> 2f8a9b5c1234 # 🔄 仅应用层重新构建
禁用分层功能
kotlin
tasks.named<BootJar>("bootJar") {
layered {
enabled.set(false)
}
// 或者排除工具 JAR
includeTools.set(false)
}
使用 PropertiesLauncher 🔧
PropertiesLauncher
提供了更灵活的启动配置选项:
kotlin
tasks.named<BootWar>("bootWar") {
manifest {
attributes("Main-Class" to "org.springframework.boot.loader.launch.PropertiesLauncher")
}
}
通过 loader.properties
文件可以配置额外的类路径、系统属性等:
properties
# loader.properties
loader.path=lib/,file:///opt/app/lib/
loader.main=com.example.MyApplication
最佳实践总结 ✅
1. 选择合适的打包方式
选择指南
- JAR 打包:适用于微服务、容器化部署
- WAR 打包:需要部署到传统 Servlet 容器时使用
- 分层 JAR:Docker 环境下推荐使用
- 完全可执行:需要在 Linux 服务器上作为服务运行时使用
2. 依赖管理最佳实践
kotlin
dependencies {
// 运行时依赖
implementation("org.springframework.boot:spring-boot-starter-web")
// 仅编译时需要的依赖
compileOnly("org.springframework.boot:spring-boot-configuration-processor")
// 开发时依赖(不会打包到生产环境)
developmentOnly("org.springframework.boot:spring-boot-devtools")
// WAR 部署时由容器提供的依赖
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
}
3. 构建配置优化
kotlin
tasks.named<BootJar>("bootJar") {
// 明确指定主类,避免自动检测的不确定性
mainClass.set("com.example.MyApplication")
// 使用有意义的文件名
archiveFileName.set("${project.name}-${project.version}.jar")
// 启用分层以优化 Docker 构建
layered {
enabled.set(true)
}
}
通过掌握 Spring Boot Gradle 插件的可执行归档文件打包功能,我们可以轻松创建适合不同部署场景的应用程序包,大大简化了 Java 应用的分发和部署过程。无论是开发环境的快速启动,还是生产环境的容器化部署,Spring Boot 都为我们提供了强大而灵活的解决方案。