1. 简介

本文将介绍如何利用 spring-boot-thin-launcher 项目,将 Spring Boot 应用打包为 Thin JAR(瘦 JAR)

众所周知,Spring Boot 默认采用“Fat JAR”(胖 JAR)部署方式 —— 即一个可执行的 JAR 包中包含了应用代码及其所有依赖。这种方式简单直接✅,但在微服务架构下容易造成资源浪费❌:多个服务重复打包相同的依赖库,占用大量存储和传输带宽。

而 Thin JAR 的核心思想是:只打包应用自身的代码,依赖在运行时动态解析和加载。这样可以显著减小发布包体积,提升 CI/CD 效率,特别适合容器化部署场景。


2. 前置条件

要使用 Thin JAR,你的项目必须是一个标准的 Spring Boot 项目。本文主要覆盖 Maven 和 Gradle 两种主流构建工具的配置方式。

⚠️ 注意:以下内容基于 Spring Boot 2.x 及以上版本。虽然 Thin Launcher 也支持更早版本,但 Gradle 配置略有不同,这里不再展开。

2.1 Maven 项目

确保你的 pom.xml 中已配置 Spring Boot 插件:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>    
</plugin>

并且通过 BOM 或父 POM 统一管理版本,例如:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.5</version>
    <relativePath/>
</parent>

这是标准做法,就不多解释了。

2.2 Gradle 项目

Gradle 项目需要引入 Spring Boot 插件和依赖管理插件:

buildscript {
    ext {
        springBootPlugin = 'org.springframework.boot:spring-boot-gradle-plugin'
        springBootVersion = '3.1.5'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("${springBootPlugin}:${springBootVersion}")
    }
}

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

springBoot {
    mainClassName = 'com.example.DemoApplication'
}

同样,这是典型的 Boot 项目结构。


3. Thin JAR 是如何工作的?

Spring Boot Thin Launcher 是一个轻量级启动器,它的作用是:

  1. 从 JAR 包中读取依赖声明(来自 pom.xmlthin.properties
  2. 通过 Maven 仓库下载缺失的依赖到本地缓存
  3. 动态构建类路径并启动应用主类

✅ 所以最终生成的 JAR 只包含:

  • 你的业务代码
  • 一个描述依赖的元数据文件
  • Thin Launcher 的引导逻辑

运行时才去拉依赖,相当于把“打包体积”换成了“首次启动时间”,这个权衡在某些场景下非常值得。


4. 基本用法

和普通 Fat JAR 一样,启动命令仍然是:

java -jar my-app-1.0.jar

区别在于,这个 JAR 是“瘦”的,启动时会触发依赖解析流程。你也可以传入额外参数控制 Thin Launcher 行为,比如 --thin.dryrun=true 做预加载(后文会讲)。

4.1 Maven:启用 Thin JAR

只需在 spring-boot-maven-plugin 中添加一个依赖即可启用 Thin Layout:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <dependencies>
        <!-- 启用 Thin JAR 支持 -->
        <dependency>
            <groupId>org.springframework.boot.experimental</groupId>
            <artifactId>spring-boot-thin-layout</artifactId>
            <version>1.0.11.RELEASE</version>
        </dependency>
    </dependencies>
</plugin>

然后正常执行 mvn install 构建,生成的就是 Thin JAR。

✅ 小技巧:如果想同时支持 Fat 和 Thin 打包,可以把上述配置放在一个独立的 Maven Profile 中,按需激活。

4.2 Maven:生成 thin.properties

默认情况下,Thin Launcher 会读取 JAR 内嵌的 pom.xml 来解析依赖。但你也可以让 Maven 预先生成一个更完整的 thin.properties 文件,它包含所有直接和间接依赖,优先级高于 pom.xml

使用如下命令生成:

mvn org.springframework.boot.experimental:spring-boot-thin-maven-plugin:properties -Dthin.output=src/main/resources/META-INF

执行后会在指定目录生成 thin.properties,内容类似:

#Generated by org.springframework.boot.experimental.maven.ThinPropertiesMojo
artifactId=my-app
groupId=com.example
version=1.0.0
...

⚠️ 注意:输出目录必须事先存在,否则命令会失败。

4.3 Gradle:启用 Thin JAR

Gradle 需要引入专用插件:

buildscript {
    ext {
        thinPlugin = 'org.springframework.boot.experimental:spring-boot-thin-gradle-plugin'
        thinVersion = '1.0.11.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("${thinPlugin}:${thinVersion}")
    }
}

apply plugin: 'maven'
apply plugin: 'org.springframework.boot.experimental.thin-launcher'

然后执行:

./gradlew thinJar

即可生成 Thin JAR。

4.4 Gradle:自定义 pom.xml

如前所述,Thin Launcher 依赖 pom.xml 解析依赖。Gradle 项目中,这个文件由 thinPom 任务生成,并自动成为 jar 任务的依赖。

如果你想自定义 pom.xml 内容,可以添加如下任务:

task createPom {
    def basePath = 'build/resources/main/META-INF/maven'
    doLast {
        pom {
            withXml(dependencyManagement.pomConfigurer)
        }.writeTo("${basePath}/${project.group}/${project.name}/pom.xml")
    }
}

并将其加入 bootJar 的依赖链:

bootJar.dependsOn = [createPom]

这样就能控制最终打包进 JAR 的 pom.xml 内容。

4.5 Gradle:生成 thin.properties

和 Maven 类似,Gradle 也能生成 thin.properties 替代 pom.xml

对应的任务是 thinProperties,默认不启用。你需要手动将其加入构建依赖:

bootJar.dependsOn = [thinProperties]

之后构建时就会生成 thin.properties 文件。


5. 依赖的存储与缓存策略

Thin JAR 的本质是“延迟加载依赖”,这些依赖并不会消失,只是换了个地方存。Thin Launcher 使用标准 Maven 机制来解析依赖:

  1. ✅ 先查本地仓库(默认 ~/.m2/repository
  2. ❌ 缺失则从远程仓库(如 Maven Central)下载
  3. 💾 下载后缓存到本地,下次启动复用

显然,第 2 步是性能瓶颈和失败高发区——毕竟网络不可靠。为了避免每次启动都下载,有几种预加载方案。

5.1 预热运行(Warm-up)

最简单的办法是在目标环境先跑一次应用,把依赖提前下载好:

java -jar my-app-1.0.jar

但这样会真正启动应用,可能带来副作用(比如连接数据库、发消息等)。

✅ 推荐做法:使用 dry run(空跑)模式,只下载依赖不启动应用:

java -Dthin.dryrun=true -jar my-app-1.0.jar

或等价写法:

java -jar my-app-1.0.jar --thin.dryrun=true

✅ 支持 Spring Boot 风格的参数传递,THIN_DRYRUN 环境变量也行,只要不是 false 就生效。

5.2 构建时打包依赖(推荐)

更稳妥的方式是在 CI 构建阶段就把依赖下载好,和应用一起部署。

这样部署环境无需联网,启动更快更稳定。

依赖会被组织成标准 Maven 仓库结构:

deps/
  repository/
    com/
    org/
    net/
    ...

运行时通过 --thin.root 指定该目录:

java -jar my-app-1.0.jar --thin.root=./deps

多个应用的依赖可以安全合并(直接拷贝覆盖即可),形成一个共享仓库。

5.3 Maven:构建时下载依赖

使用 spring-boot-thin-maven-pluginresolve 目标:

<plugin>
    <groupId>org.springframework.boot.experimental</groupId>
    <artifactId>spring-boot-thin-maven-plugin</artifactId>
    <version>1.0.11.RELEASE</version>
    <executions>
        <execution>
            <id>resolve</id>
            <goals>
                <goal>resolve</goal>
            </goals>
        </execution>
    </executions>
</plugin>

构建后会在 target/thin/root/ 下生成完整的依赖树。

5.4 Gradle:构建时下载依赖

Gradle 使用 thinResolve 任务:

./gradlew thinResolve

执行后会在 build/thin/root/ 目录生成依赖文件,结构与 Maven 一致。

部署时连同应用 JAR 一起拷贝过去,启动时用 --thin.root 指向该目录即可。


6. 总结与延伸阅读

Thin JAR 是一种轻量级部署方案,适合对包体积敏感的场景,比如:

  • 微服务集群(减少镜像层重复)
  • CI/CD 流水线(加快构建和传输)
  • 资源受限环境

核心优势是“小”,代价是“首次启动慢”或“需预加载依赖”。合理使用 --thin.rootthin.dryrun 能很好平衡这一点。

📌 官方项目主页:https://github.com/dsyer/spring-boot-thin-launcher
包含更多实战指南,比如 Heroku 部署、Docker 集成等。

📌 示例代码:

建议集合,踩坑时回来翻一翻。


原始标题:Thin JARs with Spring Boot