序章:神秘的 "Fat JAR"
一切始于一个普通的命令:java -jar my-app.jar
。但 Spring Boot 的 JAR 文件并不普通,它被称为 "Fat JAR"(或可执行 JAR)。它的内部结构是精心设计的,这是它能够独立运行的秘密。
my-app.jar
├─ BOOT-INF/
│ ├─ classes/ <-- 你编写的业务代码 (.class文件)
│ │ └─ com/example/MyApplication.class
│ └─ lib/ <-- 所有的依赖JAR包 (如 tomcat-embed, spring-web, etc.)
│ ├─ tomcat-embed-core-9.0.58.jar
│ └─ spring-web-5.3.15.jar
├─ META-INF/
│ └─ MANIFEST.MF <-- JVM的入口点清单文件
└─ org/springframework/boot/loader/... <-- Spring Boot自己的加载器代码
java -jar
无法直接读取嵌套JAR中的类,因此 Spring Boot 需要一个特殊的“引导员”。
第一幕:JVM的唤醒与“引导员”登场
1. JVM 寻找入口
执行 java -jar
时,JVM会读取 JAR 包内 META-INF/MANIFEST.MF
文件,寻找 Main-Class
属性来确定程序的入口。
# 在 MANIFEST.MF 中:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.MyApplication <-- 你的主类
MyApplication
,而是 Spring Boot 提供的一个特殊“引导员”:JarLauncher
。
2. 特殊的类加载器
JarLauncher
的首要任务是创建一个特殊的类加载器:LaunchedURLClassLoader
。
这个类加载器的“特异功能”就是能够读取并加载嵌套在 BOOT-INF/lib/
目录下的 JAR 文件中的类。它打破了 Java 的常规限制。
LaunchedURLClassLoader
,我们的应用才能在一个单一的 Fat JAR 文件中,无缝地使用所有依赖,而无需手动解压。
3. 启动真正的“主角”
JarLauncher
完成了环境准备后,会从 MANIFEST.MF
中读取 Start-Class
属性的值,找到你真正的应用主类(例如 com.example.MyApplication
)。
然后,它使用刚刚创建的 LaunchedURLClassLoader
来加载这个主类,并调用其 main
方法。至此,程序的控制权正式交给了 Spring Boot 的核心启动逻辑。
JarLauncher
说:“舞台准备好了!”MyApplication.main(args)
登场!第二幕:SpringApplication.run()
的宏大叙事
当你的 `main` 方法调用 `SpringApplication.run(MyApplication.class, args)` 时,Spring Boot 的核心启动流程正式拉开序幕。这个过程可以被看作是一系列的事件广播和响应。
4. 准备阶段:环境与监听器
Spring Boot 首先会创建一个 SpringApplication
实例。然后它会:
- 判断应用类型(Servlet Web, Reactive Web, 或非 Web 应用)。
- 从
classpath
下的META-INF/spring.factories
文件中加载并设置好各种ApplicationContextInitializer
和ApplicationListener
。 - 广播第一个事件: ApplicationStartingEvent
spring.factories
是 Spring Boot 自动配置的基石。这个阶段就像是电影开拍前,导演(SpringApplication
)在召集所有工作人员(监听器和初始化器)。
5. 环境构建与“云”的介入
接下来,Spring Boot 会创建并准备 Environment
对象,它负责管理所有的配置属性。
- 加载所有来源的配置属性,并按优先级排序(命令行 > 系统变量 > application.properties 等)。
- 广播事件: ApplicationEnvironmentPreparedEvent
[Spring Cloud 特有流程]:如果这是一个 Spring Cloud 应用,BootstrapApplicationListener
会监听到这个事件,并在这里“暂停”主流程。它会创建一个临时的、父级的 "bootstrap" 上下文,专门用来连接配置中心(如 Nacos、Consul),拉取远程配置,并将其添加到 Environment
中。完成后,主流程继续。
6. 创建 IoC 容器 (ApplicationContext)
万事俱备,Spring Boot 开始创建应用的核心——IoC容器 ApplicationContext
。
- 根据应用类型选择合适的上下文实现(如
AnnotationConfigServletWebServerApplicationContext
)。 - 将前面准备好的
Environment
和所有 Bean 定义加载到上下文中。 - 广播事件: ApplicationContextInitializedEvent
7. 刷新容器:Bean的实例化与Web服务器启动
这是整个启动流程中最核心、最复杂的一步:调用 context.refresh()
方法。这个方法会触发一系列的重量级操作:
- Bean 定义扫描与处理:处理
@Configuration
类,扫描@Component
等注解,将 Bean 定义注册到容器中。 - Bean 实例化与依赖注入:实例化所有非懒加载的单例 Bean,并处理
@Autowired
等注解,完成依赖注入。 - 启动内嵌 Web 服务器:如果是一个 Web 应用,此时会创建并启动内嵌的 Tomcat、Jetty 或 Undertow。
刷新完成后,广播事件: ApplicationPreparedEvent
refresh()
是 Spring IoC 容器的“点火”开关。它完成了从“蓝图”(Bean 定义)到“实体建筑”(活生生的对象实例)的转变,并启动了对外服务的窗口(Web 服务器)。
第三幕:终章与就绪
8. 启动完成,执行自定义任务
容器已经完全准备就绪。此时 Spring Boot 会查找并调用所有实现了 ApplicationRunner
和 CommandLineRunner
接口的 Bean。这是留给开发者的一个钩子,可以在应用启动后立即执行一些自定义的初始化代码。
在 Runner 执行之后,广播最后一个关键事件: ApplicationReadyEvent
9. 应用运行中
main
方法此时已经执行完毕,但应用并不会退出。因为像 Tomcat 请求处理线程、数据库连接池线程等都是非守护(Daemon)线程,它们会阻止 JVM 的退出。
应用现在进入了稳定运行状态,静静地等待着网络请求的到来。
如果在启动过程中任何一步发生无法恢复的错误,Spring Boot 会广播 ApplicationFailedEvent,然后退出程序。