SpringBoot应用启动流程解析
SpringBoot应用启动流程解析
每个使用 Spring Boot 的开发者都极为熟悉的入口:SpringApplication.run()。这个简单的静态方法调用,背后隐藏着极其精妙和复杂的启动逻辑。文本文将围绕两大核心部分展开:SpringApplication 的构造阶段和run 方法的执行阶段。
1、SpringApplication 的构造
当我们调用 SpringApplication.run(Application.class, args) 时,实际上首先创建了一个 SpringApplication 实例。这个构造过程是整个启动流程的“准备阶段”,它加载了一系列至关重要的配置信息。让我们看看 SpringApplication 的构造方法里发生了什么。
1 | |
1.1 主配置类 (Primary Sources)
构造方法的第一件事,就是将传入的 primarySources(通常是带有 @SpringBootApplication 注解的类)保存到一个 LinkedHashSet 中。LinkedHashSet 的使用保证了元素的唯一性和有序性。这为后续的组件扫描和自动配置提供了最核心的配置源。
1.2 推断 Web 应用类型
WebApplicationType.deduceFromClasspath() 这一步非常关键。它通过检查类路径中是否存在特定的类,来判断当前应用的类型:
- REACTIVE: 存在
DispatcherHandler但不存在DispatcherServlet和ServletContainer时,判定为响应式 Web 应用。 - SERVLET: 存在
javax.servlet.Servlet和ConfigurableWebApplicationContext时,判定为传统的 Servlet Web 应用。 - NONE: 如果上述条件均不满足,则为非 Web 应用。
这个判断结果直接决定了后面创建什么样的 ApplicationContext 和嵌入式 Web 服务器。
1.3 加载 Spring 工厂 (spring.factories)
这是 Spring Boot 最核心的 SPI (Service Provider Interface) 机制。getSpringFactoriesInstances 方法会去加载所有 META-INF/spring.factories 文件,并根据传入的类型(如 ApplicationContextInitializer.class)获取对应的实现类全名,然后实例化它们。
这个过程加载了三种关键的组件:
- BootstrapRegistryInitializer: 用于初始化引导注册表,这是在 Spring Boot 2.4.0 引入的新特性,主要用于更早期的启动阶段,例如 Spring Cloud 的引导上下文初始化。
- ApplicationContextInitializer: 这是 Spring 框架原生的回调接口,允许我们在
ConfigurableApplicationContext执行refresh()之前对其进行自定义设置。 - ApplicationListener: 加载各种应用事件监听器,它们会监听
SpringApplication在启动不同阶段发布的事件,实现解耦和扩展。
1.4 推断主入口类
deduceMainApplicationClass() 通过分析当前线程的调用堆栈,找到包含 main 方法的那个类。这个类通常就是我们的入口类,它将被用于日志打印和一些内部逻辑判断。
至此,SpringApplication 对象初始化完毕,它已经收集了所有必要的“弹药”,准备开始真正的启动冲锋。
2、核心发动机 - run 方法的执行
SpringApplication.run(String... args) 方法是整个启动流程的“总控室”。它的代码结构清晰,步骤分明。我们将深入每一个步骤,看看它们究竟做了什么。
1 | |
2.1 启动引导与监听机制
- StopWatch: 一个简单的计时器,精确记录 Spring Boot 启动的总耗时。
- createBootstrapContext(): 创建
DefaultBootstrapContext,并调用之前加载的BootstrapRegistryInitializer对其进行初始化。这为应用启动的最早期提供了一个脱离ApplicationContext的注册中心,可以注册一些非常基础的、在容器刷新前就必须使用的对象。 - configureHeadlessProperty(): 设置
java.awt.headless系统属性为true。这在服务器环境下至关重要,它告诉 JVM 即使没有显示器、键盘等外设,也可以以“无头模式”运行,进行图像处理等操作。 - SpringApplicationRunListeners: 这是一个包装器,内部持有一组
SpringApplicationRunListener。getRunListeners(args)同样通过spring.factories加载它们(默认只有一个实现类:EventPublishingRunListener)。这些监听器是整个启动流程的“传感器”,它们会在不同的启动阶段调用相应的方法(如starting、environmentPrepared、contextPrepared等),而这些方法的默认实现就是发布对应的 Spring 事件(如ApplicationStartingEvent)。listeners.starting(): 触发ApplicationStartingEvent,标志着应用启动的最早期。
2.2 环境准备 (Environment Preparation)
Environment 是 Spring 应用中的核心抽象,它代表了应用运行时的环境,包含了无数的配置属性。
- ApplicationArguments: 对传入的
main方法参数args进行封装,方便后续使用。 - prepareEnvironment(): 这是一个复杂的过程,我们深入进去看看:
1 | |
getOrCreateEnvironment(): 根据之前推断的webApplicationType创建对应类型的Environment。例如,Servlet 环境会创建StandardServletEnvironment。configureEnvironment(): 将命令行参数包装成SimpleCommandLinePropertySource并添加到Environment的PropertySource链中。ConfigurationPropertySources.attach(): 这是一个巧妙的设计,它将Environment中的PropertySource适配为 Spring Boot 的ConfigurationPropertySource,以便使用@ConfigurationProperties进行类型安全的属性绑定。listeners.environmentPrepared(): 发布ApplicationEnvironmentPreparedEvent。这是极其重要的一步,所有外部化配置(如application.properties、application.yml、OS 环境变量等)都是在这个事件被触发后,通过PropertySourceLoader解析并添加到Environment中的。监听这个事件的ConfigFileApplicationListener(或新版本的EnvironmentPostProcessor)完成了这一壮举。
在 prepareEnvironment 返回后,configureIgnoreBeanInfo(environment) 会被调用,设置 spring.beaninfo.ignore 属性,优化 JavaBeans Introspector 的缓存。
2.3 Banner 打印
printBanner(environment) 负责在控制台打印那个我们熟悉的 ASCII 艺术字。它可以从类路径、文件系统或通过 ResourceLoader 自定义 Banner 的内容。打印完后,Banner 对象会被保存下来,后续可能会注册到容器中。
2.4 应用上下文的创建与准备
createApplicationContext(): 又是一个基于webApplicationType的策略模式。对于 Servlet 环境,它会创建AnnotationConfigServletWebServerApplicationContext。这个类继承了ServletWebServerApplicationContext,而后者是 Spring Boot 嵌入式 Web 容器的关键所在。prepareContext(): 这是启动过程中一个非常关键的“粘合剂”步骤,将前面准备好的各种组件(Environment、Banner、Listeners 等)与刚创建的ApplicationContext关联起来。
1 | |
postProcessApplicationContext(): 设置BeanFactory的类加载器,注册ConversionService等默认组件。applyInitializers(): 遍历并调用在构造阶段加载的所有ApplicationContextInitializer的initialize方法,对上下文进行最后的定制。listeners.contextPrepared(): 发布ApplicationContextInitializedEvent,此时上下文已创建,初始化器也已执行完毕,但尚未加载 Bean 定义。bootstrapContext.close(context): 将引导上下文中注册的实例迁移到主ApplicationContext中,然后关闭引导上下文。load(): 这是非常重要的一步,它将我们的主配置类(primarySources)通过BeanDefinitionLoader解析成BeanDefinition,并注册到ApplicationContext的BeanFactory中。正是这一步,让我们的@SpringBootApplication注解类成为一个配置类,为后续的自动配置和组件扫描奠定了基础。listeners.contextLoaded(): 发布ApplicationPreparedEvent,标志着 Bean 定义已加载完毕,容器已经准备好进行最终的初始化了。
2.5 刷新上下文 - refreshContext()
refreshContext(context) 是整个启动流程的高潮部分。它直接调用了 AbstractApplicationContext.refresh() 方法,这是 Spring 框架的“圣杯”方法,承载着 IoC 容器初始化的所有核心逻辑。Spring Boot 通过其创建的 ApplicationContext 实现类,巧妙地在这个原生流程中插入了启动嵌入式 Web 服务器的逻辑。
1 | |
我们聚焦于 refresh() 方法的几个关键步骤:
prepareRefresh(): 刷新前的准备工作。obtainFreshBeanFactory(): 获取或创建新的BeanFactory。prepareBeanFactory(): 配置BeanFactory的标准特性。postProcessBeanFactory(): 这是模板方法,留给子类覆写。invokeBeanFactoryPostProcessors(): 执行BeanFactoryPostProcessor,其中就包括我们熟知的ConfigurationClassPostProcessor,它会解析@Configuration类,处理@ComponentScan、@Import等注解,完成自动配置的加载和 BeanDefinition 的注册。registerBeanPostProcessors(): 注册BeanPostProcessor。initMessageSource(): 初始化消息源。initApplicationEventMulticaster(): 初始化事件广播器。onRefresh(): 又一个模板方法,这里是 Spring Boot 启动嵌入式 Web 服务器的关键时刻!ServletWebServerApplicationContext覆写了这个方法:
1 | |
在 onRefresh() 阶段,ServletWebServerApplicationContext 会从 BeanFactory 中获取 ServletWebServerFactory(如 TomcatServletWebServerFactory),然后调用其 getWebServer 方法,创建并启动 Tomcat/Jetty/Undertow 等嵌入式 Web 服务器。
finishBeanFactoryInitialization(): 实例化所有剩余的单例(非延迟初始化)Bean。finishRefresh(): 刷新完成,发布ContextRefreshedEvent。
至此,一个功能完备的 Spring 容器已经启动完毕,内嵌的 Web 服务器也已开始监听端口。
2.6 启动后动作
afterRefresh(): 一个空的模板方法,留给开发者或子类在容器刷新完成后做自定义扩展。listeners.started(): 发布ApplicationStartedEvent,表明 Spring 容器已经启动,但尚未调用ApplicationRunner和CommandLineRunner。callRunners(): 查找容器中所有实现了ApplicationRunner或CommandLineRunner接口的 Bean,并按@Order排序后依次执行它们的run方法。这是留给开发者进行启动后数据初始化、预加载等操作的绝佳位置。listeners.running(): 发布ApplicationReadyEvent,标志着应用已经成功启动并准备就绪,可以接收请求了。
2.7 异常处理
如果在启动的任何环节抛出异常,handleRunFailure 方法会被调用。它会尝试发布 ApplicationFailedEvent,并执行异常报告器(SpringBootExceptionReporter)来输出有意义的错误信息,帮助开发者快速定位问题。
3、Spring Boot内嵌Tomcat
3.1 触发点:onRefresh() 与 createWebServer()
在 Spring Boot 的启动流程中,refreshContext(context) 最终调用了 AbstractApplicationContext.refresh()。其中,一个关键步骤是调用模板方法 onRefresh(),而 ServletWebServerApplicationContext(它是 Spring Boot 为 Servlet 环境提供的 ApplicationContext 实现)重写了该方法:
1 | |
也就是说,当 IoC 容器刷新进入 onRefresh 阶段时,就会触发内嵌 Web 服务器的创建。createWebServer() 的核心逻辑如下:
1 | |
这里有两个关键点:获取工厂和使用工厂创建 WebServer。
3.2 获取 ServletWebServerFactory
getWebServerFactory() 会从当前的 BeanFactory 中查找类型为 ServletWebServerFactory 的 Bean。由于我们引入了 spring-boot-starter-web,自动配置类 ServletWebServerFactoryAutoConfiguration 会生效,它根据类路径上的情况,向容器中注册对应的工厂 Bean。例如,如果存在 Tomcat 相关的类(Tomcat 本身),则会注册 TomcatServletWebServerFactory。Spring Boot 的自动配置逻辑如下(简化):
1 | |
所以,通常情况下我们得到的就是 TomcatServletWebServerFactory 实例。
3.3 创建内嵌 Tomcat:factory.getWebServer(initializer)
接下来进入 TomcatServletWebServerFactory.getWebServer(ServletContextInitializer... initializers) 方法。这个方法会完成 Tomcat 服务器的实例化、配置和启动准备。
3.3.1 构建 Tomcat 实例
1 | |
3.3.2 准备 TomcatEmbeddedContext
prepareContext() 方法负责在 Tomcat 的 Host 下添加一个 Context(即 Web 应用上下文),并且将我们传入的 ServletContextInitializer(即 getSelfInitializer() 返回的那个)与 Tomcat 的启动机制关联起来。
1 | |
这里最重要的就是 **TomcatStarter**。它负责在 Tomcat 启动时,回调我们传入的 ServletContextInitializer,从而将 Spring 的 DispatcherServlet 等组件注册到原生的 ServletContext 中。
3.4 ServletContextInitializer 的作用:注册 DispatcherServlet
回到 ServletWebServerApplicationContext.getSelfInitializer() 方法:
1 | |
selfInitialize 方法实际上会被封装为 ServletContextInitializer,然后被 TomcatStarter 在 Tomcat 启动时调用。当 TomcatStarter.onStartup() 被执行时,它会遍历所有 ServletContextInitializer(包括我们传入的这个),调用它们的 onStartup 方法,完成对 ServletContext 的编程式配置。这正是 Spring MVC 的 DispatcherServlet 被注册到内嵌 Tomcat 的时机。
3.5 启动内嵌 Tomcat:TomcatWebServer.start()
factory.getWebServer(...) 返回的是一个 TomcatWebServer 对象,它包装了前面创建的 Tomcat 实例。此时 Tomcat 还没有真正启动(没有绑定端口、没有接受请求)。Tomcat 的启动时机在 finishRefresh() 阶段:
1 | |
startWebServer() 最终调用 TomcatWebServer.start():
1 | |
tomcat.start() 会触发一系列组件的启动,包括 Connector(开始监听端口)、Engine、Host、Context 等。在启动 Context 时,TomcatStarter.onStartup() 被调用,进而执行 Spring 的 ServletContextInitializer,注册 DispatcherServlet 等组件。至此,内嵌 Tomcat 完全启动,可以处理 HTTP 请求。
3.6 优雅停止与 Shutdown Hook
Spring Boot 还注册了关闭钩子,确保在 JVM 退出时能优雅地停止内嵌 Tomcat:
1 | |
并且通过 AbstractApplicationContext.registerShutdownHook() 在容器关闭时调用 stop() 方法,从而释放端口、清理资源。