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 组件的版本号,它会自动匹配。

我的经验: 千万别手动写版本号。比如 spring-boot-starter-web 的版本,让插件去管。我曾经见过一个项目,手动写了 2.7.0 的版本,结果和 Spring Boot 3.x 的插件冲突,编译直接挂掉。

插件注册后,会自动添加几个关键任务:

  • bootJar — 打包可执行 jar
  • bootRun — 直接运行应用
  • bootBuildImage — 构建 Docker 镜像

这些任务都是 Gradle 原生 jarrun 任务的增强版。说白了,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 检测到变化就重启。效率很高。

小技巧: 如果你用 IntelliJ IDEA,可以直接在 Gradle 面板双击 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
  • 依赖层和加载器层直接从缓存拿,构建时间从几分钟降到几秒
核心收益: 在 CI/CD 流水线中,分层 Jar 能大幅减少镜像构建时间。我参与的一个项目,原来每次构建要 3 分钟,分层后降到 15 秒。说白了,就是「把不变的东西缓存起来,只变该变的」。

19.5 知识体系图

下面这张图展示了 Spring Boot 项目构建的核心逻辑:

Spring Boot 项目构建核心逻辑 Spring Boot Gradle 插件 bootJar(打包可执行 Jar) bootRun(开发时快速启动) 分层 Jar 依赖层 / 加载器层 / 应用层 热部署支持 DevTools + 自动重启 目标:开箱即用 · 快速迭代 · 高效部署

19.6 实战建议

最后,我总结几条实战中容易踩的坑:

  1. 版本对齐 — Spring Boot 插件版本必须和 Spring Boot 版本一致。比如 3.2.0 的插件配 3.2.0 的依赖,混用会出奇怪的问题。
  2. 排除内置容器 — 如果你要用外置 Tomcat,记得在 bootJar 里排除内嵌容器。不过我个人不推荐这么做,Spring Boot 的优势就是内嵌容器。
  3. 分层 Jar 的缓存策略 — 在 CI 中,确保 Docker 的 layer 缓存正确。我见过有人把 COPY 顺序写反了,结果每次都要重新下载依赖。
  4. bootRun 的内存配置 — 默认 JVM 参数可能不够。我习惯在 build.gradle 里加 jvmArgs = ['-Xmx512m'],避免开发时 OOM。
我曾经踩过的坑: 有一次升级 Spring Boot 2.6 到 3.0,忘了更新插件版本。结果 bootJar 打出来的包启动时报错 ClassNotFoundException。查了半天才发现是插件版本不匹配。从那以后,我每次升级都会先检查 plugins 块里的版本号。

嗯,Spring Boot 项目构建其实不复杂。核心就是理解插件、任务、分层这三个概念。你只要把 bootJarbootRun 用熟了,再配合分层 Jar 优化 Docker 构建,基本就能应对大部分场景了。