Spring Boot 的 .jar
包能够直接运行,主要是因为它采用了 Spring Boot Loader 作为自定义类加载器,并且使用了 可执行 JAR(fat jar) 的结构。以下是详细的技术原理:
1. Spring Boot 可执行 JAR 的结构
Spring Boot 生成的 JAR 包不是传统的 Java JAR,而是 fat jar(可执行 JAR),它包含:
- 应用的代码(
com.example.*
) - 所有依赖的第三方库
- Spring Boot Loader(Spring Boot 特有的类加载器)
- META-INF 目录,包含特殊的
MANIFEST.MF
配置 - 嵌套的 JAR 依赖
一个典型的 Spring Boot JAR 结构:
your-app.jar
│── META-INF/
│── org/springframework/boot/loader/ # Spring Boot 自定义的类加载器
│── BOOT-INF/
│ ├── classes/ # 你的应用代码
│ ├── lib/ # 所有依赖的 JAR 文件
│ ├── layers.idx # Spring Boot 2.3+ 支持的分层 JAR
│── application.properties # 应用配置文件
其中,BOOT-INF/lib/
目录存放了应用依赖的所有 JAR,而 BOOT-INF/classes/
目录存放应用的代码和资源文件。
2. Spring Boot Loader
机制
Spring Boot 的核心组件 spring-boot-loader
提供了一个自定义的类加载器 Launcher
,用于加载嵌套 JAR(即 BOOT-INF/lib/
下的依赖)。
关键类
JarLauncher
(默认):用于运行 fat jar,自动识别BOOT-INF/lib/
下的依赖。WarLauncher
:用于运行 WAR 包的 Spring Boot 应用。PropertiesLauncher
:支持从application.properties
加载自定义配置。
当执行 java -jar your-app.jar
时,JarLauncher
负责:
- 解压并加载嵌套 JAR:
- 由于传统 Java 类加载器不支持 JAR 内嵌 JAR,Spring Boot 使用
LaunchedURLClassLoader
解析BOOT-INF/lib/
下的 JAR 文件。
- 由于传统 Java 类加载器不支持 JAR 内嵌 JAR,Spring Boot 使用
- 定位
Main-Class
并启动:MANIFEST.MF
中定义了:Main-Class: org.springframework.boot.loader.JarLauncher
JarLauncher
解析BOOT-INF/classes/META-INF/MANIFEST.MF
,找到Start-Class
并运行:Start-Class: com.example.Application
- 这相当于执行:
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
- 优先加载
BOOT-INF/classes/
里的代码,然后再加载BOOT-INF/lib/
里的依赖。
3. java -jar
如何解析 Spring Boot JAR
Spring Boot JAR 采用了一种特殊的 Zip 目录结构,使 java -jar
能够正确解析:
- 传统 JAR:所有
.class
文件和依赖在lib/
目录。 - Spring Boot JAR:使用
spring-boot-loader
解析BOOT-INF/lib/
和BOOT-INF/classes/
。
Spring Boot 通过 修改 JAR 的 MANIFEST.MF
文件,让 java -jar
解析 JarLauncher
,而不是 Java 默认的 sun.misc.Launcher
。
查看 MANIFEST.MF
:
jar -xf your-app.jar META-INF/MANIFEST.MF
cat META-INF/MANIFEST.MF
你会看到类似的内容:
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.Application
当你运行:
java -jar your-app.jar
实际上等效于:
java -cp your-app.jar org.springframework.boot.loader.JarLauncher
然后 JarLauncher
负责解析 Start-Class
并启动 Spring Boot 应用。
4. 为什么普通 JAR 不能直接运行?
普通 JAR 没有 Spring Boot 的 JarLauncher
机制,也不会将所有依赖打包,因此:
- 需要手动指定
-cp
(类路径):java -cp my-app.jar:lib/* com.example.Application
- 不能嵌套 JAR,必须将所有 JAR 解压到
lib/
。
而 Spring Boot JAR 通过 JarLauncher
解决了这些问题,使得它可以直接运行。
5. Spring Boot JAR 与传统 WAR 的区别
特性 | Spring Boot JAR | 传统 WAR 部署 |
---|---|---|
运行方式 | java -jar app.jar | 部署到 Tomcat |
是否包含 Tomcat | 内置(默认 TomcatEmbeddedServletContainer ) | 需要外部 Tomcat |
依赖管理 | BOOT-INF/lib/ | WEB-INF/lib/ |
类加载机制 | JarLauncher + LaunchedURLClassLoader | Servlet 容器类加载 |
6. Spring Boot JAR 可以直接运行的原因总结
-
Spring Boot Loader 机制
- 通过
JarLauncher
解析嵌套 JAR,绕开传统 Java 类加载限制。 org.springframework.boot.loader
提供了LaunchedURLClassLoader
,可以加载BOOT-INF/lib/
目录下的依赖。
- 通过
-
自定义 MANIFEST.MF
- 通过
Main-Class: org.springframework.boot.loader.JarLauncher
让java -jar
解析 Spring Boot 自定义的类加载逻辑。 Start-Class
指定应用的main
入口。
- 通过
-
嵌套 JAR 结构
- 依赖 JAR 被放在
BOOT-INF/lib/
目录中,而非传统lib/
,可以自动加载。
- 依赖 JAR 被放在
-
无需外部 Tomcat
- Spring Boot 内置 Tomcat/Jetty/Undertow 服务器,使得 Web 应用可以像普通 JAR 一样运行。
7. 其他运行方式
除了 java -jar
,Spring Boot 还支持:
- 直接运行
main()
mvn spring-boot:run
- 使用
java -cp
运行java -cp your-app.jar org.springframework.boot.loader.JarLauncher
- Docker 部署
FROM openjdk:17 COPY target/app.jar /app.jar ENTRYPOINT ["java", "-jar", "/app.jar"]
8. 结论
Spring Boot 之所以可以直接运行 .jar
,是因为:
- 使用
JarLauncher
解析BOOT-INF/lib/
里的依赖 - 自定义
MANIFEST.MF
让java -jar
识别Spring Boot Loader
- 支持嵌套 JAR,避免
CLASSPATH
手动管理 - 内置 Tomcat,避免传统 WAR 部署麻烦
这使得 Spring Boot 应用更加 便捷,可以一键打包、一键部署,非常适合云环境和微服务架构。