19、Spring Boot项目构建:Spring Boot插件、BootJar与BootRun、分层Jar
Spring Boot 项目构建,说白了就是让一个 Java 应用能「开箱即用」。你想想看,传统 Java 项目要部署,得装 Tomcat、配环境、打 war 包,麻烦得很。Spring Boot 插件把这些事全干了——打包成可执行 jar,内嵌容器,一行命令就能跑起来。
我个人习惯,每次新建 Spring Boot 项目,第一件事就是检查 build.gradle 里的插件配置。这步要是错了,后面全是坑。
19.1 Spring Boot Gradle 插件
Spring Boot 插件是 Gradle 生态里最核心的插件之一。它的作用很简单:把你的应用打包成一个可独立运行的 jar。
配置方式如下:
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
}
这里有个细节——io.spring.dependency-management 插件。它负责管理 Spring Boot 的依赖版本。你不需要手动指定每个 Spring 组件的版本号,它会自动匹配。
插件注册后,会自动添加几个关键任务:
- bootJar — 打包可执行 jar
- bootRun — 直接运行应用
- bootBuildImage — 构建 Docker 镜像
这些任务都是 Gradle 原生 jar 和 run 任务的增强版。说白了,Spring Boot 插件就是在原生任务上「套了一层壳」。
19.2 BootJar:打包可执行 Jar
bootJar 是 Spring Boot 项目最常用的任务。它会把你的代码、依赖、内嵌容器全部打包到一个 jar 文件里。
默认配置下,你只需要执行:
./gradlew bootJar
生成的 jar 在 build/libs/ 目录下。直接 java -jar xxx.jar 就能启动。
但这里有个关键点——可执行 jar 和普通 jar 不一样。普通 jar 只是把 class 文件打包,可执行 jar 需要特殊的目录结构:
my-app.jar
├── BOOT-INF/
│ ├── classes/ # 你的代码
│ └── lib/ # 所有依赖 jar
├── META-INF/
│ └── MANIFEST.MF # 启动入口
└── org/
└── springframework/
└── boot/
└── loader/ # Spring Boot 加载器
嗯,这里要注意。如果你用 jar 任务而不是 bootJar,打出来的包是没法直接运行的。我刚开始用 Gradle 时就犯过这个错——执行 ./gradlew jar,然后 java -jar 报错说「没有主清单属性」。
bootJar,不要用 jar。如果你两个任务都配置了,记得把 jar 禁用掉:
jar {
enabled = false
}
19.3 BootRun:开发时快速启动
bootRun 是开发阶段最常用的任务。它不需要打包,直接编译并启动应用。
./gradlew bootRun
默认情况下,它会使用 src/main/resources 下的配置文件。如果你想指定不同的 profile:
./gradlew bootRun --args='--spring.profiles.active=dev'
或者直接在 build.gradle 里配置:
bootRun {
jvmArgs = ['-Dspring.profiles.active=dev']
}
我个人习惯在开发时用 bootRun 配合热部署。改完代码,IDE 自动编译,Spring Boot DevTools 检测到变化就重启。效率很高。
bootRun,或者在终端里执行。我个人更推荐终端,因为能看到完整的日志输出。
19.4 分层 Jar:优化 Docker 镜像构建
这是 Spring Boot 2.3 引入的特性。为什么要分层?因为 Docker 镜像构建时,依赖层很少变化,代码层经常变化。如果每次改代码都要重新下载所有依赖,那构建速度就太慢了。
分层 Jar 把 jar 包拆成多个层:
layers.idx 文件内容示例:
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "BOOT-INF/lib/*-SNAPSHOT.jar"
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"
启用分层很简单:
bootJar {
layered {
enabled = true
}
}
然后构建 Docker 镜像时,可以这样写 Dockerfile:
FROM eclipse-temurin:17-jre as builder
WORKDIR /app
COPY build/libs/my-app.jar .
RUN java -Djarmode=layertools -jar my-app.jar extract
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
这样做的效果很明显:
- 第一次构建时,所有层都会缓存
- 后续只改代码,只重新构建
application层 - 依赖层和加载器层直接从缓存拿,构建时间从几分钟降到几秒
19.5 知识体系图
下面这张图展示了 Spring Boot 项目构建的核心逻辑:
19.6 实战建议
最后,我总结几条实战中容易踩的坑:
- 版本对齐 — Spring Boot 插件版本必须和 Spring Boot 版本一致。比如 3.2.0 的插件配 3.2.0 的依赖,混用会出奇怪的问题。
- 排除内置容器 — 如果你要用外置 Tomcat,记得在
bootJar里排除内嵌容器。不过我个人不推荐这么做,Spring Boot 的优势就是内嵌容器。 - 分层 Jar 的缓存策略 — 在 CI 中,确保 Docker 的 layer 缓存正确。我见过有人把
COPY顺序写反了,结果每次都要重新下载依赖。 - bootRun 的内存配置 — 默认 JVM 参数可能不够。我习惯在
build.gradle里加jvmArgs = ['-Xmx512m'],避免开发时 OOM。
bootJar 打出来的包启动时报错 ClassNotFoundException。查了半天才发现是插件版本不匹配。从那以后,我每次升级都会先检查 plugins 块里的版本号。
嗯,Spring Boot 项目构建其实不复杂。核心就是理解插件、任务、分层这三个概念。你只要把 bootJar 和 bootRun 用熟了,再配合分层 Jar 优化 Docker 构建,基本就能应对大部分场景了。