乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Boot 概述

    • 章节导读:Spring Boot概述与核心理念
    • Spring Boot是什么
    • Spring Boot与Spring Framework的关系
    • 约定优于配置
  • 第2章 快速入门与第一个应用

    • 章节导读:快速入门与第一个应用
    • SpringApplication
    • 第一个Spring Boot应用
  • 第3章 起步依赖与版本管理

    • 章节导读:起步依赖与版本管理
    • 起步依赖
    • BOM版本管理
  • 第4章 自动配置原理

    • 章节导读:自动配置原理
    • 自动配置原理
    • AutoConfigurationImportSelector
    • 自动配置报告
  • 第5章 核心注解

    • 章节导读:核心注解
    • @SpringBootApplication
    • @EnableAutoConfiguration
    • @ConditionalOnClass
    • @ConditionalOnMissingBean
    • @ConditionalOnBean
    • @ConditionalOnProperty
  • 第6章 外部化配置与属性绑定

    • 章节导读:外部化配置与属性绑定
    • 外部化配置
    • @ConfigurationProperties
    • @Value
    • 配置属性优先级
  • 第7章 Profile 与环境切换

    • 章节导读:Profile与环境切换
    • @Profile
    • 多环境配置文件
  • 第8章 内嵌服务器与部署

    • 章节导读:内嵌服务器与部署
    • 内嵌服务器
    • Fat Jar
  • 第9章 Actuator 与监控

    • 章节导读:Actuator与监控
    • Actuator Health
    • Actuator Info
    • Actuator Metrics
    • 自定义Endpoint
    • 自定义HealthIndicator
  • 第10章 开发工具与最佳实践

    • 章节导读:开发工具与最佳实践
    • Banner自定义
    • 热部署

Fat JAR

一句话定位:Spring Boot 通过 spring-boot-maven-plugin 将应用代码与所有依赖打包为可执行的 Fat JAR,利用 JarLauncher 和 BOOT-INF 目录结构实现依赖隔离加载,达成 java -jar 一键运行的部署体验。


定义与作用

传统的 Java Web 应用打包产出为 WAR 文件,其中仅包含项目自身的 WEB-INF/classes 和 WEB-INF/lib,运行时依赖外部 Servlet 容器提供标准库(如 Servlet API)。这种方式的问题在于:部署环境必须与开发环境的容器版本匹配,依赖冲突排查困难,回滚时需要替换整个容器目录。

Spring Boot 的 Fat JAR(可执行 JAR) 将以下三类内容全部打包进一个 JAR 文件:

内容传统 WAR 位置Spring Boot Fat JAR 位置说明
项目编译后的类文件WEB-INF/classes/BOOT-INF/classes/业务代码、配置类
项目依赖的第三方 JARWEB-INF/lib/BOOT-INF/lib/Spring Framework、数据库驱动、连接池等
启动器类WEB-INF/web.xml 或容器控制META-INF/MANIFEST.MF 指定 Main-ClassJarLauncher 作为入口

关键差异:Fat JAR 的 BOOT-INF 目录是 Spring Boot 自定义的,标准 JVM 类加载器不会自动扫描它。Spring Boot 通过自定义的 JarLauncher 及其类加载器 LaunchedURLClassLoader 实现对 BOOT-INF/classes 和 BOOT-INF/lib/*.jar 的隔离加载。


适用位置与常用属性

Fat JAR 的构建完全由 Maven 插件控制,开发者在 pom.xml 中配置 spring-boot-maven-plugin。

插件配置位置

<!-- pom.xml -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.18</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!-- 常用配置属性 -->
                <mainClass>com.feixiang.student.StudentManagementApplication</mainClass>
                <layout>JAR</layout>
                <excludeDevtools>true</excludeDevtools>
            </configuration>
        </plugin>
    </plugins>
</build>

常用配置属性

属性说明典型值
<mainClass>显式指定启动类全限定名com.feixiang.student.StudentManagementApplication
<layout>打包布局类型JAR(默认)、WAR、ZIP
<excludeDevtools>是否排除 devtoolstrue(生产环境建议)
<classifier>附加分类器后缀exec
<executable>Linux 下生成可执行脚本头true

注意:如果 POM 继承了 spring-boot-starter-parent,则插件版本号可省略,由父 POM 统一管理。


核心原理

Fat JAR 内部结构

结构解读:

  • META-INF/MANIFEST.MF:Main-Class 指向 Spring Boot 的 JarLauncher,而非业务启动类;真正的业务主类记录在 Start-Class 中。
  • org/springframework/boot/loader/:Spring Boot Loader 模块的类文件,直接位于 JAR 根目录,由标准 JVM 类加载器加载。
  • BOOT-INF/classes/:项目本身的编译输出,标准类加载器不可见。
  • BOOT-INF/lib/:所有 Maven 依赖的 JAR 包,标准类加载器不可见。

启动加载流程

流程解读:

  1. java -jar 命令由 JVM 的标准类加载器(AppClassLoader)加载 JAR 根目录下的 JarLauncher。
  2. JarLauncher 构造 LaunchedURLClassLoader,将 BOOT-INF/classes/ 和 BOOT-INF/lib/ 下的每个 JAR 解析为 URL 资源。
  3. LaunchedURLClassLoader 作为子类加载器,负责后续所有 Spring Boot 类和依赖类的加载,实现与系统类路径的隔离。
  4. 通过反射调用 Start-Class(即业务启动类)的 main 方法,启动 Spring 容器。

完整示例

场景说明

飞翔科技的运维团队要求小崔将学生成绩管理系统打包为可执行 JAR,以便在测试服务器上直接 java -jar 运行。小崔需要验证打包后的 JAR 结构正确,且能独立启动。

操作前:使用普通 Maven 打包

<!-- 错误示范:pom.xml 中未配置 spring-boot-maven-plugin -->
<build>
    <plugins>
        <!-- 只有 maven-jar-plugin,这是普通 JAR 打包 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
        </plugin>
    </plugins>
</build>

执行打包:

$ mvn clean package

生成的 target/student-app-1.0.0.jar 仅包含项目自身的类文件,没有 Spring Boot Loader,也没有 BOOT-INF/lib 目录。运行结果:

$ java -jar target/student-app-1.0.0.jar
no main manifest attribute, in target/student-app-1.0.0.jar

或即使指定了主类,也会因缺少依赖而抛出 ClassNotFoundException: org.springframework.boot.SpringApplication。

使用该特性的完整代码

小崔在 pom.xml 中引入正确的插件:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
    </parent>

    <groupId>com.feixiang</groupId>
    <artifactId>student-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.feixiang.student.StudentManagementApplication</mainClass>
                    <excludeDevtools>true</excludeDevtools>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

执行打包:

$ mvn clean package

操作后运行结果及分析

打包成功后,目录结构如下:

target/
├── student-app-1.0.0.jar          ← 原始 JAR(maven-jar-plugin 生成)
└── student-app-1.0.0.jar.original ← repackage 前的备份

注意:spring-boot-maven-plugin 的 repackage 目标会在 maven-jar-plugin 打包完成后,将其重命名为 .jar.original,然后生成新的 Fat JAR 覆盖原文件名。

查看 JAR 内部结构:

$ jar tf target/student-app-1.0.0.jar | head -30
META-INF/
META-INF/MANIFEST.MF
org/
org/springframework/
org/springframework/boot/
org/springframework/boot/loader/
org/springframework/boot/loader/JarLauncher.class
BOOT-INF/
BOOT-INF/classes/
BOOT-INF/classes/com/
BOOT-INF/classes/com/feixiang/
BOOT-INF/classes/com/feixiang/student/
BOOT-INF/classes/com/feixiang/student/StudentManagementApplication.class
BOOT-INF/classes/application.yml
BOOT-INF/lib/
BOOT-INF/lib/spring-boot-2.7.18.jar
BOOT-INF/lib/spring-boot-starter-web-2.7.18.jar
BOOT-INF/lib/spring-web-5.3.31.jar
BOOT-INF/lib/spring-webmvc-5.3.31.jar
BOOT-INF/lib/tomcat-embed-core-9.0.83.jar
...

启动验证:

$ java -jar target/student-app-1.0.0.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.7.18)

2024-05-20 10:30:15.456  INFO 9876 --- [main] o.s.b.w.e.t.TomcatWebServer : Tomcat started on port(s): 8080 (http)

分析:

  1. JarLauncher 被 JVM 正确加载,进而启动 LaunchedURLClassLoader。
  2. BOOT-INF/classes 中的业务类和 application.yml 被正确加载。
  3. BOOT-INF/lib 中的 Tomcat、Spring Web 等依赖被隔离加载,无类路径冲突。
  4. 内嵌 Tomcat 随 Spring 容器启动,应用可直接对外提供 HTTP 服务。

易错场景与面试考点

易错场景一:使用 maven-jar-plugin 代替 spring-boot-maven-plugin

<!-- 错误示范:忘记配置 spring-boot-maven-plugin -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.2</version>
</plugin>

后果:生成的 JAR 缺少 JarLauncher 和 BOOT-INF 结构,无法通过 java -jar 运行。小崔在测试环境部署时反复排查,最后发现 POM 中根本没有 Spring Boot 打包插件。

正确做法:始终使用 spring-boot-maven-plugin,并确保 repackage 目标被执行。继承 spring-boot-starter-parent 是最稳妥的方式。

易错场景二:生产 JAR 中误含 devtools

<!-- 错误示范:未排除 devtools -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

同时 pom.xml 的 <dependencies> 中保留了:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

后果:生产环境 JAR 中携带了 devtools,存在性能开销(额外监听文件变化)和潜在安全风险(远程触发重启漏洞)。

正确做法:插件配置中显式添加 <excludeDevtools>true</excludeDevtools>,或将 devtools 依赖的 <scope> 设为 runtime(虽然 Spring Boot 推荐 optional)。

面试考点

Q:Spring Boot 的 Fat JAR 和普通 JAR 有什么区别?

Fat JAR 通过 spring-boot-maven-plugin 的 repackage 目标生成,内部包含 BOOT-INF/classes(业务类)、BOOT-INF/lib(所有依赖 JAR)和 JarLauncher 启动器。普通 JAR 仅包含项目自身类,依赖需外部提供。

Q:JarLauncher 的作用是什么?

JarLauncher 是 Spring Boot Loader 模块提供的入口类,由 JVM 标准类加载器加载。它负责创建 LaunchedURLClassLoader,将 BOOT-INF 下的类和依赖 JAR 加入类路径,然后通过反射调用业务启动类的 main 方法。

Q:为什么 Spring Boot 不直接把业务启动类设为 Main-Class?

因为业务启动类及其依赖位于 BOOT-INF 目录下,标准 JVM 类加载器无法直接识别该路径。需要 JarLauncher 先用标准类加载器加载,再由自定义类加载器接管后续类加载。

Q:如何查看 Fat JAR 内部的依赖列表?

使用 jar tf app.jar | grep BOOT-INF/lib 可列出所有打包的依赖 JAR 文件名;或解压后通过 BOOT-INF/classpath.idx(Spring Boot 2.3+)查看。

Q:repackage 目标的具体行为是什么?

repackage 在 maven-jar-plugin 执行完毕后,将原始 JAR 重命名为 .original,然后创建新的 Fat JAR,覆盖原始文件名。新 JAR 包含 Loader 类和 BOOT-INF 结构。

上一页
内嵌服务器