为什么 Spring Boot 项目可以直接执行?(从打包到启动的完整原理)
Spring Boot 项目能通过 java -jar xxx.jar 直接执行,核心原因是它打包生成的并非普通 JAR 文件,而是经过 spring-boot-maven-plugin 特殊处理的可执行 JAR(Executable JAR)—— 这种 JAR 内置了 “启动器” 和 “自定义类加载器”,能自动加载依赖、初始化 Spring 上下文,最终启动应用。从 “打包结构→启动器原理→类加载机制” 三个维度,彻底讲透 Spring Boot 直接执行的底层逻辑。
先明确:Spring Boot 打包的不是普通 JAR
普通 Java 项目打包的 JAR(如 Maven 的 jar 插件生成的)仅包含项目自身的 .class 文件,无法直接通过 java -jar 执行(会报 “没有主清单属性” 错误),因为它缺少两个关键要素:
- 明确的 “启动类” 配置(告诉 JVM 入口在哪);
- 依赖的第三方 JAR 包(普通 JAR 不会包含依赖,需手动指定类路径)。
而 Spring Boot 项目通过 spring-boot-maven-plugin 打包后,生成的 JAR 结构和配置完全不同 —— 它解决了 “依赖加载” 和 “启动入口” 两个核心问题
关键文件:MANIFEST.MF 中的启动配置
MANIFEST.MF 是 JAR 文件的 “清单文件”,位于 META-INF/ 目录下,用于描述 JAR 的元信息(如版本、主类)。Spring Boot 生成的 MANIFEST.MF 包含 3 个核心配置,直接决定了 “能否直接执行”:
1 | # 1. JVM 实际执行的主类(Spring Boot 启动器) |
这三个配置的作用至关重要,我们逐一拆解:
1. Main-Class:JVM 的 “启动器”——JarLauncher
Main-Class 是 JVM 执行 java -jar 时的入口类(即 JVM 会先调用这个类的 main 方法)。
Spring Boot 没有直接将我们的 Application 类设为 Main-Class,而是用了 org.springframework.boot.loader.JarLauncher—— 它是 Spring Boot 内置的 “启动工具类”,核心职责是:
- 加载
BOOT-INF/lib目录下的第三方依赖 JAR(如 Spring 核心包、数据库驱动); - 加载
BOOT-INF/classes目录下的项目自身类(我们写的 Controller、Service 等); - 最终通过反射调用
Start-Class(我们的Application类)的main方法,启动 Spring 应用。
2. Start-Class:我们的应用主类
Start-Class 配置的是真正的业务入口类(即标注 @SpringBootApplication 的类),例如该项目中的 com.zhanghe.exam.Application。
JarLauncher 会在完成 “依赖加载” 和 “类加载器初始化” 后,通过反射调用这个类的 main 方法,触发 Spring Boot 的启动流程(初始化 Spring 上下文、自动配置等)。
3. Spring-Boot-Classes/Lib:资源存放路径
这两个配置指定了 “项目类” 和 “依赖包” 的位置,是 JarLauncher 加载资源的关键:
Spring-Boot-Classes: BOOT-INF/classes/:项目自身的.class文件、配置文件(如application.yml)存放在这里;Spring-Boot-Lib: BOOT-INF/lib/:项目依赖的所有第三方 JAR(如spring-core.jar、mybatis.jar)存放在这里。
普通 JAR 不会有 BOOT-INF 目录,依赖需要通过 -classpath 参数手动指定;而 Spring Boot 的 JarLauncher 会自动扫描这两个目录,无需手动配置类路径。
核心原理:JarLauncher 如何启动应用?
JarLauncher 是 Spring Boot 直接执行的 “核心引擎”,它的 main 方法执行流程可分为 3 步,本质是 “自定义类加载→找到应用入口→反射启动”:
步骤 1:初始化自定义类加载器(LaunchedURLClassLoader)
JVM 默认的类加载器(AppClassLoader)只能加载 “当前 JAR 根目录” 或 “-classpath 指定路径” 的类,无法识别 BOOT-INF/classes 和 BOOT-INF/lib 目录下的资源。
JarLauncher 会创建一个自定义类加载器 LaunchedURLClassLoader,专门处理 Spring Boot 可执行 JAR 的特殊结构:
- 扫描
BOOT-INF/classes目录,将其转换为URL格式(如jar:file:/xxx/xxx.jar!/BOOT-INF/classes/); - 扫描
BOOT-INF/lib目录下的所有依赖 JAR,将每个 JAR 也转换为URL(如jar:file:/xxx/xxx.jar!/BOOT-INF/lib/spring-core.jar!/); - 将这些
URL作为 “类加载路径”,初始化LaunchedURLClassLoader—— 后续所有类(包括 Spring 核心类、我们的业务类)都通过这个类加载器加载。
步骤 2:找到 Start-Class 的全类名
JarLauncher 会读取 MANIFEST.MF 文件中的 Start-Class 配置,获取我们的应用主类全类名(如 com.zhanghe.exam.Application)。
步骤 3:反射调用 Start-Class 的 main 方法
通过 LaunchedURLClassLoader 加载 Start-Class 对应的 Class 对象,然后通过反射调用其 main 方法:
1 | // JarLauncher 核心逻辑简化 |
这一步之后,就会进入我们熟悉的 Spring Boot 启动流程:SpringApplication.run(Application.class, args) → 初始化 Spring 上下文 → 自动配置 → 启动嵌入式容器(如 Tomcat)。
打包插件:spring-boot-maven-plugin 的关键作用
所有上述 “特殊结构” 和 “配置”,都由 spring-boot-maven-plugin 插件在打包时自动完成 —— 它是 Spring Boot 能直接执行的 “幕后推手”,核心功能有 3 个:
1. 构建特殊的 JAR 目录结构
插件会将项目编译后的 .class 文件和配置文件,移动到 BOOT-INF/classes 目录;将所有依赖的第三方 JAR,复制到 BOOT-INF/lib 目录。最终生成的 JAR 结构如下:
1 | xxx.jar |
2. 生成正确的 MANIFEST.MF
插件会自动在 META-INF/ 目录下生成 MANIFEST.MF,并填充关键配置:
Main-Class固定为org.springframework.boot.loader.JarLauncher(或WarLauncher,若打包为 WAR);Start-Class自动读取项目的 “主类”(即包含main方法且标注@SpringBootApplication的类);Spring-Boot-Classes和Spring-Boot-Lib固定指向BOOT-INF/classes/和BOOT-INF/lib/。
3. 注入启动器类(JarLauncher 等)
插件会将 Spring Boot 启动器相关的类(如 JarLauncher、LaunchedURLClassLoader)打包到 JAR 的根目录下(org/springframework/boot/loader/),确保 JVM 能找到并执行 Main-Class。
对比:普通 JAR vs Spring Boot 可执行 JAR
为了更直观理解,我们对比两种 JAR 的核心差异:
| 对比维度 | 普通 JAR(Maven jar 插件生成) | Spring Boot 可执行 JAR(spring-boot-maven-plugin 生成) |
|---|---|---|
| 依赖存放 | 不包含依赖,需手动指定 -classpath |
依赖存放在 BOOT-INF/lib,自动加载 |
| MANIFEST.MF 配置 | 无 Main-Class 或仅指向普通类 |
含 Main-Class(JarLauncher)和 Start-Class(应用主类) |
| 类加载器 | 使用 JVM 默认类加载器(AppClassLoader) | 使用自定义 LaunchedURLClassLoader,支持 BOOT-INF 结构 |
| 执行方式 | 无法直接 java -jar(需 -classpath) |
可直接 java -jar xxx.jar 执行 |
| 核心组件 | 仅包含项目自身类 | 包含项目类、依赖、Spring Boot 启动器 |
总结:Spring Boot 直接执行的完整流程
最后,我们用一张流程图总结 Spring Boot 从 “打包” 到 “启动” 的完整链路:
- 打包阶段:
开发者执行mvn clean package→spring-boot-maven-plugin插件工作:- 构建
BOOT-INF/classes(项目类)和BOOT-INF/lib(依赖); - 生成
MANIFEST.MF,配置Main-Class=JarLauncher和Start-Class=应用主类; - 注入启动器类(JarLauncher 等)。
- 构建
- 执行阶段:
开发者执行java -jar xxx.jar→ JVM 和 JarLauncher 协同工作:- JVM 读取
MANIFEST.MF,执行JarLauncher的main方法; - JarLauncher 创建
LaunchedURLClassLoader,加载BOOT-INF下的类和依赖; - JarLauncher 反射调用
Start-Class(应用主类)的main方法; - 触发
SpringApplication.run(),启动 Spring Boot 应用
- JVM 读取
v1.3.10