两万字长文,辩证看待Spring Boot Fat JAR,结尾有惊喜
本文分析了Spring Boot Fat JAR在项目中的得与失,并面向读者提供了从入门到精通的优缺点总结
前言:Spring Boot Fat JAR 简介
在软件开发和部署的领域中,简化和效率是至关重要的。Spring Boot,作为一款流行的Java框架,提供了许多创新机制来简化开发过程。其中,“fat JAR” 技术是Spring Boot生态系统中的一个亮点,它彻底改变了我们打包和部署应用的方式。
“Fat JAR”,字面意思是"胖 JAR",是Spring Boot应用打包的一种方式,它将所有必需的依赖项和库打包到一个可执行的JAR文件中。这个"胖"的称谓,源自于它包含丰富的内容,让开发者摆脱了管理众多依赖项的繁琐。
在传统的Java应用中,部署通常需要处理多个JAR文件和库,并确保它们正确地放置在类路径上。而Spring Boot的"fat JAR"则不同,它将所有依赖项整合到一个文件中,实现了"一站式"部署。这个单一的JAR文件包含了整个应用的所有必要组件,从核心的Spring框架到第三方库,再到你的应用代码,一应俱全。
使用"fat JAR"的优势显而易见:
- 简化部署:只需一个JAR文件,即可轻松部署应用,无需担心依赖项的管理。
- 便携性:所有依赖项都包含在JAR中,使得应用易于在不同环境中运行,无需额外配置。
- 快速启动:内置的启动脚本使得应用启动变得简单,只需一条命令即可运行。
- 版本控制:所有依赖项的版本都固定在JAR中,避免了版本冲突的问题。
本文档将深入探讨Spring Boot “fat JAR” 的打包过程、其背后的机制,以及如何充分利用这一技术来优化你的应用部署。通过阅读本文,你将了解如何创建自己的"fat JAR",并体验其带来的便利和效率提升。让我们一起探索Spring Boot生态系统中的这一强大功能吧!
初识Springboot FatJar
- Fat JAR 的目录结构
example-app.jar
├── META-INF
│ ├── MANIFEST.MF # JAR 清单文件
│ └── maven/
├── BOOT-INF
│ ├── classes/ # 项目编译后的类文件
│ │ ├── com/example/
│ │ └── application.properties
│ └── lib/ # 依赖的 JAR 包
│ ├── spring-boot-3.1.0.jar
│ ├── spring-core-5.7.6.jar
│ └── ...
└── org/springframework/boot/loader/ # Spring Boot Loader 类
├── JarLauncher.class # JAR 启动器
├── LaunchedURLClassLoader.class # 自定义类加载器
└── ...
- MANIFEST.MF 文件内容
MANIFEST.MF
文件是 JAR 文件的清单文件,它包含了 JAR 文件的元数据信息,例如版本号、主类、类路径等等。一个典型的 Spring Boot fat JAR 的MANIFEST.MF
文件内容如下:
Manifest-Version: 1.0
Implementation-Title: your-application-name
Implementation-Version: your-application-version
Start-Class: org.springframework.boot.loader.JarLauncher
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Main-Class: org.springframework.boot.loader.PropertiesLauncher
Spring-Boot-Version: your-spring-boot-version
Created-By: Apache Maven {maven-version}
Build-Jdk-Spec: {java-version}
让我们逐行解释一下这些字段的含义:
- Manifest-Version: 清单文件的版本号,通常是
1.0
。 - Implementation-Title: 应用的名称。
- Implementation-Version: 应用的版本号。
- Start-Class: Spring Boot fat JAR 的入口点,通常是
org.springframework.boot.loader.JarLauncher
。这是 Spring Boot 特有的类加载器,用于加载嵌入式 JAR 中的类。 - Spring-Boot-Classes: 应用的类文件所在路径,通常是
BOOT-INF/classes/
。 - Spring-Boot-Lib: 应用的依赖库所在路径,通常是
BOOT-INF/lib/
。 - Main-Class: 应用的主类,即包含
main
方法的类。 对于 Spring Boot 应用,该值为org.springframework.boot.loader.PropertiesLauncher
,它会读取项目中的application.properties
或application.yml
配置文件,并最终启动Start-Class
中定义的JarLauncher
。 - Spring-Boot-Version: 使用的 Spring Boot 版本。
- Created-By: 创建 JAR 文件的工具,例如
Apache Maven {maven-version}
。 - Build-Jdk-Spec: 构建 JAR 文件使用的 JDK 版本。
关键字段说明:
-
Start-Class 和 Main-Class 的区别: 这是理解 Spring Boot fat JAR 执行机制的关键。
Main-Class
是PropertiesLauncher
,它负责加载外部配置文件,并最终将控制权交给Start-Class
JarLauncher
。JarLauncher
是 Spring Boot 特有的类加载器,它能够从嵌入式的 JAR 文件中加载应用的类和依赖库。 这种机制使得 Spring Boot 应用可以独立运行,无需依赖外部容器。 -
BOOT-INF/classes 和 BOOT-INF/lib: 这两个目录是 Spring Boot fat JAR 特有的结构。
BOOT-INF/classes
目录包含应用的类文件,BOOT-INF/lib
目录包含应用的依赖库。 这种结构能够防止应用的类和依赖库与其他 JAR 文件冲突。
自定义 MANIFEST.MF:
可以通过 Maven 或 Gradle 的插件来定制 MANIFEST.MF
文件的内容,例如添加自定义属性或修改现有属性的值。
了解 MANIFEST.MF
文件的内容对于理解 Spring Boot fat JAR 的运行机制至关重要。 通过分析该文件,可以了解应用的版本、依赖、入口点等关键信息。
3. 启动原理
JarLauncher
作为 Spring Boot fat JAR 的入口点,扮演着至关重要的角色。 它的主要作用是创建一个自定义的类加载器,用于加载 fat JAR 中的应用程序类和依赖库。 让我们更详细地了解一下它的工作机制:
JarLauncher
的作用:
-
创建
LaunchedURLClassLoader
:JarLauncher
的核心功能是创建LaunchedURLClassLoader
。这是一个自定义的类加载器,它能够从 fat JAR 内部的BOOT-INF/classes
和BOOT-INF/lib
目录加载应用程序类和依赖库。 -
设置线程上下文类加载器:
JarLauncher
会将创建的LaunchedURLClassLoader
设置为当前线程的上下文类加载器。 这确保了应用程序代码和依赖库能够被正确加载和访问。 -
启动应用程序主类:
JarLauncher
会使用LaunchedURLClassLoader
加载应用程序的主类(由Main-Class
属性指定,通常是org.springframework.boot.loader.PropertiesLauncher
),并调用其main
方法,从而启动应用程序。
JarLauncher
的优势:
-
隔离应用程序依赖: 通过自定义类加载器,
JarLauncher
将应用程序的依赖库与运行环境隔离开来,避免了依赖冲突。 这意味着 fat JAR 可以在任何环境中运行,无需担心与已安装的库发生冲突。 -
简化部署: 由于所有依赖都打包在 fat JAR 中,部署应用程序变得非常简单,只需将 fat JAR 复制到目标环境即可运行,无需配置额外的依赖。
-
支持嵌入式 Web 服务器:
JarLauncher
支持在 fat JAR 中嵌入 Web 服务器(例如 Tomcat、Jetty 或 Undertow),使得可以直接运行 Web 应用程序,无需外部 Web 服务器。
JarLauncher
的类型:
Spring Boot 提供了三种类型的 JarLauncher
:
-
JarLauncher
: 这是最基本的JarLauncher
,用于从 JAR 文件启动应用程序。 -
WarLauncher
: 用于从 WAR 文件启动应用程序,主要用于部署到传统的 Servlet 容器中。 -
PropertiesLauncher
: 这是 Spring Boot 默认使用的JarLauncher
。 它会在启动前读取应用程序的配置文件(application.properties
或application.yml
),并将配置信息传递给应用程序。
JarLauncher
和 PropertiesLauncher
的关系:
在 Spring Boot fat JAR 中,MANIFEST.MF
文件中的 Main-Class
属性通常指定为 PropertiesLauncher
。 PropertiesLauncher
会首先加载应用程序的配置文件,然后启动 JarLauncher
来加载应用程序类和依赖库。 这种机制允许 Spring Boot 应用程序根据不同的配置文件进行不同的配置。
总而言之,JarLauncher
是 Spring Boot fat JAR 能够独立运行的关键组件。它通过自定义类加载器隔离应用程序依赖,简化了部署流程,并支持嵌入式 Web 服务器。 理解 JarLauncher
的工作机制对于开发和部署 Spring Boot 应用程序至关重要。
// 1. JarLauncher 作为入口类
public class JarLauncher extends ExecutableArchiveLauncher {
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
// 2. 自定义类加载器
public class LaunchedURLClassLoader extends URLClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 优先从 BOOT-INF/classes 加载
// 然后从 BOOT-INF/lib 中的 JAR 加载
// 最后委托给父类加载器
}
}
// 3. 启动流程
class ExecutableArchiveLauncher {
protected void launch(String[] args) throws Exception {
// 1. 注册 URL 协议处理器
JarFile.registerUrlProtocolHandler();
// 2. 创建类加载器
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 3. 加载启动类
launch(args, getMainClass(), classLoader);
}
}
- Maven 打包配置
在 Maven 中打包 Spring Boot fat JAR,需要使用
spring-boot-maven-plugin` 插件。 以下是几种常见的配置方式,以及它们的区别和适用场景:
1. 默认配置 (最常用):
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这是最简单的配置方式,无需任何额外的配置。插件会自动检测 Spring Boot 应用程序,并将其打包成可执行的 fat JAR。
2. 指定主类:
如果你的项目结构比较复杂,或者有多个带有 main
方法的类,你可以显式指定主类:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.YourMainClass</mainClass>
</configuration>
</plugin>
</plugins>
</build>
将 com.example.YourMainClass
替换为你实际的主类名。
3. 自定义 MANIFEST.MF 文件:
你可以通过配置 spring-boot-maven-plugin
来定制 MANIFEST.MF
文件的内容,例如添加自定义属性:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true