如果你曾好奇:为什么在 main 方法里执行一行 SpringApplication.run(),整个应用就能启动并对外服务?这一节我们就深入 SpringApplication 的源码,拆解 Spring Boot 启动的完整过程。理解这些步骤,不仅有助于排查启动时的诡异问题,也能让你对自动配置、事件监听、扩展点有更立体的认识。
26.4.1 SpringApplication.run() 的两大阶段
从调用入口看:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
run 静态方法内部会先创建一个 SpringApplication 实例,然后调用实例方法 run。整个启动被清晰地分为 准备阶段 和 运行阶段。
26.4.2 准备阶段:new SpringApplication(primarySources)
SpringApplication 的构造器主要做了两件事:推断应用类型、加载初始化器与监听器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath(); // ①
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); // ②
setInitializers(
getSpringFactoriesInstances(ApplicationContextInitializer.class)); // ③
setListeners(getSpringFactoriesInstances(ApplicationListener.class)); // ④
this.mainApplicationClass = deduceMainApplicationClass(); // ⑤
}
① 推断 Web 类型:deduceFromClasspath() 会检查 classpath 下是否存在 DispatcherServlet 等关键类,决定创建 SERVLET、REACTIVE 还是普通的 ApplicationContext。
②-④ 加载扩展点:通过 SpringFactoriesLoader 从 META-INF/spring.factories(Spring Boot 2.x)或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中读取并实例化 ApplicationContextInitializer 和 ApplicationListener 等。这些是 Spring Boot 留给开发者的常用扩展接口。
⑤ 推断主类:通过检查调用栈,找到包含 main 方法的类并记录下来。
实用提醒:
ApplicationContextInitializer和ApplicationListener是定制启动行为的两个核心入口。如果你希望在容器刷新之前对上下文做一些个性化配置,可以实现前者并通过spring.factories注册。
26.4.3 运行阶段:实例方法 run(String... args)
这是真正的启动流程,核心代码在 SpringApplication.run() 实例方法中。我把整个过程拆成 10 个关键步骤,并附上对应的源码片段。
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // 1
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args); // 2
listeners.starting(bootstrapContext, this.mainApplicationClass); // 3
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
bootstrapContext, applicationArguments); // 4
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); // 5
context = createApplicationContext(); // 6
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment,
listeners, applicationArguments, printedBanner); // 7
refreshContext(context); // 8
afterRefresh(context, applicationArguments); // 9
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
listeners.started(context, timeTakenToStartup); // 10
callRunners(context, applicationArguments); // 11
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady); // 12
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
下面逐一解析每个步骤。
1. 创建 BootstrapContext(Spring Boot 2.4+)
DefaultBootstrapContext 是一个轻量级的引导上下文,用于在环境准备之前共享资源(如配置信息的早期来源)。它会在环境准备好后被关闭,其生命周期比 ApplicationContext 更短。
2. 获取 SpringApplicationRunListeners
从 spring.factories 加载 SpringApplicationRunListener 的实现,默认只有 EventPublishingRunListener,它会将启动过程中的各个里程碑转化为 Spring 事件,广播给之前加载的 ApplicationListener。
3. 发布 starting 事件
listeners.starting(...) 标志着启动正式开始。此时 ApplicationContext 还未创建,开发者可以通过监听 ApplicationStartingEvent 做一些非常早期的处理(比如初始化日志框架)。
4. 准备 Environment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext,
ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
// ... 移动到SpringApplication中处理
bindToSpringApplication(environment);
// 如果有自定义的转换逻辑,此处还会执行
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
- 根据 Web 类型创建对应的
Environment(如StandardServletEnvironment)。 - 将命令行参数、系统属性、配置文件等添加到环境中。
- 发布
ApplicationEnvironmentPreparedEvent事件,此时ConfigFileApplicationListener会监听到并加载application.properties/yml等配置文件。
5. 打印 Banner
如果启用了 banner,控制台会打印 Spring 的 ASCII 艺术字。你可以通过 spring.banner.location 指定自定义 banner,或实现 Banner 接口编程式打印。
6. 创建 ApplicationContext
根据之前推断的 webApplicationType 实例化对应的容器:AnnotationConfigServletWebServerApplicationContext(Servlet)、AnnotationConfigReactiveWebServerApplicationContext(响应式)或 AnnotationConfigApplicationContext(普通)。
7. 准备上下文 (prepareContext)
这一步在 refresh 之前,将各种准备好的零件安装到刚创建的空容器中:
private void prepareContext(DefaultBootstrapContext bootstrapContext,
ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments,
Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context); // 调用所有 ApplicationContextInitializer
listeners.contextPrepared(context);
// 关闭 BootstrapContext
bootstrapContext.close(context);
// 将启动参数、Banner 等注册为 Bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 加载 sources,即主配置类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
关键点:
applyInitializers会执行所有ApplicationContextInitializer,允许你在refresh前对上下文进行编程式调整。load(context, sources)把主配置类(被@SpringBootApplication标注的类)注册为 Bean 定义,它是后续自动配置的入口。
8. 刷新上下文 (refreshContext)
调用 AbstractApplicationContext.refresh(),这是 Spring Framework 的核心启动方法,也是整个过程中最“重”的一步。主要内容包括:
prepareRefresh():准备刷新,设置启动时间、属性源等。obtainFreshBeanFactory():解析配置类,扫描并注册所有 Bean 定义。prepareBeanFactory():注册内置 Bean,如environment、systemProperties。postProcessBeanFactory():留给子类扩展,例如 Web 容器的上下文会在这里注册ServletContext。Spring Boot 的自动配置入口ConfigurationClassPostProcessor也会在这一阶段解析@Configuration类。invokeBeanFactoryPostProcessors():调用所有BeanFactoryPostProcessor,其中ConfigurationClassPostProcessor负责处理@ComponentScan、@Import、@ImportResource等,并触发@EnableAutoConfiguration的加载逻辑,最终完成自动配置。registerBeanPostProcessors():注册BeanPostProcessor(如AutowiredAnnotationBeanPostProcessor)。initMessageSource()、initApplicationEventMulticaster():国际化与事件广播器。onRefresh():由子类实现。Web 容器上下文在此创建内嵌的 Web 服务器(Tomcat/Jetty/Undertow)并启动。registerListeners():注册已存在的ApplicationListenerBean。finishBeanFactoryInitialization():实例化所有非懒加载的单例 Bean。finishRefresh():发布ContextRefreshedEvent,启动 Web 服务器完成并接受请求。
9. afterRefresh
afterRefresh 是一个空实现,留给子类覆盖。如果你需要自定义 SpringApplication,可以在此处添加容器刷新后的额外处理。
10. 发布 started 事件
listeners.started(context, timeTakenToStartup) 发布 ApplicationStartedEvent。此时 ApplicationContext 已刷新,但 CommandLineRunner 和 ApplicationRunner 还未执行。你可以利用这个事件执行在应用真正“就绪”之前的逻辑。
11. 调用 Runner
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
这两个 Runner 是 Spring Boot 提供的应用启动后的回调接口。常用于启动后加载缓存、预加载数据或打印启动信息。ApplicationRunner 和 CommandLineRunner 的区别仅在于参数封装不同,前者是 ApplicationArguments,后者是原始 String... args。
12. 发布 ready 事件
listeners.ready(context, timeTakenToReady) 发布 ApplicationReadyEvent。此时整个启动流程完全结束,应用真正处于可用状态。你可以在此时执行一些必须等技术完全就绪后才能进行的操作。
26.4.4 启动过程的事件体系
Spring Boot 在启动过程中会依次发布这些事件:
ApplicationStartingEvent– 启动开始ApplicationEnvironmentPreparedEvent– 环境准备完成ApplicationContextInitializedEvent– Context 创建并经过初始化后ApplicationPreparedEvent– Context 准备完成,即将刷新ApplicationStartedEvent– Context 刷新完成,Runner 执行前ApplicationReadyEvent– Runner 执行完毕,应用已就绪ApplicationFailedEvent– 启动过程中出现异常
理解这些事件顺序,就能在合适的时机插入自定义逻辑。例如,你可以在 ApplicationEnvironmentPreparedEvent 中添加自定义的 PropertySource,或者在 ApplicationReadyEvent 中记录“服务已启动”的标记。
26.4.5 总结:启动流程全景图
简而言之一句话:SpringApplication 通过事件驱动的方式,串联起 Environment 准备、Context 创建、自动配置加载、Web 服务器启动、Runner 回调等步骤。其中 refreshContext 是 Spring Framework 的经典核心,而 Spring Boot 在它的前后添加了大量便于扩展的钩子(Listener、Initializer、Runner),使得框架在保持强大能力的同时,又极为灵活。
下一次当你面对一个“启动半天没反应”的问题时,不妨顺着这个流程去排查:是 SpringFactories 加载卡住?是某个自动配置类耗时过长?还是 Web 服务器端口被占用?掌握了启动源码的脉络,你就不再是一个只会使用注解的开发者,而是可以真正驾驭框架的人。