1.概述
Spring Boot 提供了简化的配置和自动化功能,使开发者能够快速构建应用。但在 Spring Boot 的背后,启动过程涉及一系列复杂的流程,包括 Bean 的加载、环境配置、自动配置的注册等。这篇博文将详细总结 Spring Boot 的启动过程,并结合源码解析每一步具体做了什么。
2.Spring Boot启动过程
当我们启动一个 Spring Boot 应用时,入口通常是一个带有 @SpringBootApplication 注解的类。@SpringBootApplication是自动配置的核心所在,详细总结可查看之前梳理的文章:Spring Boot自动配置原理详解和自定义封装实现starter
在启动过程中,会发生以下主要步骤:
- 启动引导:
SpringApplication实例创建 - 准备应用上下文
- 加载和解析配置文件
- 创建并刷新 IOC 容器
- 执行初始化器、监听器、Runner 等组件
- 启动完成事件通知
接下来我们就分别来看看这些步骤都具体干了些啥。
2.1 启动引导:创建 SpringApplication 实例
我们都知道一个应用的启动入口类通常包含如下代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SpringApplication.run() 是启动的核心方法。在源码中:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
可以看出这里的核心操作就是创建 SpringApplication 实例,设置启动args参数执行#run()方法。
2.2 准备应用上下文
在构造 SpringApplication 对象时即new SpringApplication(primarySources),会判断应用的类型(Servlet、Reactive 或 None),并初始化环境。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 确定web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 加载初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
核心处理操作如下:
- 确定 Web 应用类型:根据类路径(如是否存在
DispatcherServlet)判断是 Web、Reactive 还是 CLI 应用。
- 加载初始化器:从
META-INF/spring.factories中加载所有ApplicationContextInitializer。
- 加载监听器:从
META-INF/spring.factories中加载ApplicationListener,用于监听事件。
SpringApplication实例对象构建好之后,同时意味着上下文也准备好了,接下来就是执行#run()方法:这是启动的核心逻辑所在:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 环境准备
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 启动完成事件通知
listeners.started(context, timeTakenToStartup);
//
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
2.3 环境准备:加载和解析配置文件
Spring Boot 需要读取 外部配置文件(如 application.properties 或 application.yml)并将其解析为环境变量。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
关键步骤:
- 创建或获取 Environment 实例:使用
StandardEnvironment创建默认环境。
- 解析配置文件:读取
application.yml和application.properties。
- 发布环境事件:调用
listeners.environmentPrepared()通知所有监听器,环境已准备完毕。
2.4 创建并刷新 IOC 容器
2.4.1 创建IOC容器
在 Spring Boot 中,ApplicationContext 是 IOC 容器的核心。在启动时,会根据应用类型创建适当的上下文类(如 AnnotationConfigServletWebServerApplicationContext)。
private ConfigurableApplicationContext createApplicationContext() {
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(this.applicationContextClass);
}
上下文创建流程:
- 判断上下文类型:Servlet Web 应用使用
AnnotationConfigServletWebServerApplicationContext。
- 创建上下文实例:通过反射实例化上下文类。
- 关联环境与上下文:将
Environment注入上下文。
2.4.2 刷新IOC容器
IOC 容器的创建和刷新是核心步骤。该步骤会实例化所有需要的 Bean,并完成依赖注入。
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
}
refresh() 是核心方法,定义在 AbstractApplicationContext 中:
@Override
public void refresh() throws BeansException, IllegalStateException {
// 1. 准备上下文环境
prepareRefresh();
// 2. 创建 BeanFactory 并加载所有 Bean 定义
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 初始化 BeanFactory
prepareBeanFactory(beanFactory);
// 4. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 5. 初始化 MessageSource、事件广播器等组件
initMessageSource();
initApplicationEventMulticaster();
// 6. 注册监听器并广播早期事件
registerListeners();
// 7. 初始化所有非延迟加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 8. 完成刷新流程
finishRefresh();
}
解析
- 加载 Bean 定义:从类路径或 XML 文件中读取所有 Bean 定义。
- 创建 BeanFactory:创建 IOC 容器,用于管理 Bean 实例。
- 注册 BeanPostProcessor:允许在 Bean 初始化前后执行自定义逻辑。
- 广播事件:通知所有事件监听器。
- 初始化所有单例 Bean:创建并注入非懒加载的 Bean。
2.5 执行初始化操作
在 Spring Boot 启动过程中,ApplicationRunner 和 CommandLineRunner 会在容器初始化完成后执行。
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());
for (Object runner : runners) {
if (runner instanceof ApplicationRunner) {
((ApplicationRunner) runner).run(args);
} else if (runner instanceof CommandLineRunner) {
((CommandLineRunner) runner).run(args.getSourceArgs());
}
}
}
用于执行启动后的初始化逻辑,如缓存预加载、任务调度等
2.6 启动完成事件通知
当所有初始化工作完成后,Spring Boot 会发布 ApplicationReadyEvent,通知应用已经启动完成。
listeners.running(context);
ApplicationReadyEvent 可以用于执行某些需要在应用完全就绪后运行的逻辑,比如注册监控组件。
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:github.com/plasticene/…
Gitee地址:gitee.com/plasticene3…
微信公众号:Shepherd进阶笔记
交流探讨qun:Shepherd_126
3.总结
Spring Boot 的启动过程虽然看似简单,但背后涉及大量的逻辑处理,包括环境配置、IOC 容器初始化、事件广播、Bean 生命周期管理等。在实际开发中,理解这些流程可以帮助我们更好地优化应用的启动性能,以及在合适的阶段执行初始化逻辑。希望本文的详细讲解能帮助你深入理解 Spring Boot 的启动原理和源码实现。