Spring Boot启动流程解析
Spring Boot启动流程解析
一、概述
当我们写下这行代码启动一个Spring Boot应用时:
SpringApplication.run(Application.class, args);
看似轻描淡写的一行,实则暗藏玄机。就在这短短的一瞬间,Spring Boot已经完成了一系列精密而复杂的初始化工作——从推断应用类型到加载配置文件,从创建上下文到实例化Bean,每一步都环环相扣。本文将带你深入源码,揭开Spring Boot启动流程的神秘面纱。
二、启动流程总览
Spring Boot的启动流程宛如一场精心编排的交响乐,可以分为以下几个核心乐章:
┌─────────────────────────────────────────────────────────────────┐
│ Spring Boot 启动流程 │
└─────────────────────────────────────────────────────────────────┘
1. 创建 SpringApplication 对象
├── 推断应用类型(Servlet/Reactive/None)
├── 加载初始化器(ApplicationContextInitializer)
└── 加载监听器(ApplicationListener)
2. 执行 run() 方法
├── 准备阶段
│ ├── 获取并启动监听器
│ ├── 准备环境变量
│ └── 打印Banner
├── 创建上下文
│ ├── 创建 ApplicationContext
│ ├── 准备上下文
│ └── 执行初始化器
├── 刷新上下文
│ ├── Bean定义加载
│ ├── Bean实例化
│ └── 自动装配
└── 运行阶段
├── 执行Runner
└── 发布启动完成事件
三、SpringApplication构造过程
3.1 构造方法源码
一切始于 SpringApplication的构造。当我们调用 run()方法时,首先会创建一个 SpringApplication实例:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 1. 推断应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2. 加载初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 3. 加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4. 推断主类
this.mainApplicationClass = deduceMainApplicationClass();
}
构造方法虽然简洁,却完成了四项关键任务。
3.2 推断应用类型
Spring Boot如何知道我们的应用是传统Web应用、响应式应用,还是非Web应用?答案藏在 WebApplicationType.deduceFromClasspath()方法中:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
Spring Boot通过检测类路径上是否存在特定类来推断应用类型,这种设计既巧妙又实用:
| 应用类型 | 条件 |
|---|---|
SERVLET |
存在Servlet相关类,传统Web应用 |
REACTIVE |
存在WebFlux但不存在WebMVC,响应式应用 |
NONE |
不存在Web相关类,非Web应用 |
3.3 加载初始化器和监听器
初始化器和监听器是Spring Boot扩展机制的核心。Spring Boot通过 getSpringFactoriesInstances()方法加载它们:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 使用 SpringFactoriesLoader 加载(Spring Boot 2.4+ 同时支持 spring.factories 和新的 imports 机制)
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
注意 :Spring Boot 2.4及更高版本引入了新的配置文件加载机制。虽然
spring.factories仍然可用,但自动配置类建议迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中。
常见的初始化器:
| 初始化器 | 作用 |
|---|---|
ConfigurationWarningsApplicationContextInitializer |
报告配置警告 |
ContextIdApplicationContextInitializer |
设置上下文ID |
DelegatingApplicationContextInitializer |
委托给环境属性配置的初始化器 |
ServerPortInfoApplicationContextInitializer |
设置服务端口信息 |
常见的监听器:
| 监听器 | 作用 |
|---|---|
ConfigDataEnvironmentPostProcessor |
加载配置文件(Spring Boot 2.4+替代了原有的ConfigFileApplicationListener) |
AnsiOutputApplicationListener |
配置ANSI输出 |
LoggingApplicationListener |
配置日志系统 |
ClasspathLoggingApplicationListener |
记录类路径信息 |
四、run()方法核心流程
4.1 run()方法源码
run()方法是Spring Boot启动的核心引擎,它将各个阶段串联成一个完整的启动流程:
public ConfigurableApplicationContext run(String... args) {
// 1. 创建计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 创建 BootstrapContext(Spring Boot 2.4+引入,用于早期阶段的上下文共享)
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
// 2. 获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 3. 准备环境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 4. 打印Banner
Banner printedBanner = printBanner(environment);
// 5. 创建ApplicationContext
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 6. 准备上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 7. 刷新上下文
refreshContext(context);
// 8. 刷新后处理
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 9. 发布启动完成事件
listeners.started(context);
// 10. 执行Runner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
4.2 各阶段详解
阶段一:获取并启动监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
这一阶段主要获取 EventPublishingRunListener,它就像启动流程的"广播员",负责发布各种启动事件:
| 事件 | 触发时机 |
|---|---|
ApplicationStartingEvent |
启动开始时 |
ApplicationEnvironmentPreparedEvent |
环境准备完成时 |
ApplicationContextInitializedEvent |
上下文初始化完成时 |
ApplicationPreparedEvent |
上下文准备完成时 |
ApplicationStartedEvent |
上下文刷新完成时 |
ApplicationReadyEvent |
应用就绪时 |
阶段二:准备环境
环境准备是启动流程中至关重要的一环,它决定了应用后续如何读取配置:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建环境对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 配置属性源
ConfigurationPropertySources.attach(environment);
// 发布环境准备事件
listeners.environmentPrepared(bootstrapContext, environment);
// 绑定到SpringApplication
DefaultPropertiesPropertySource.moveToEnd(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
环境准备过程如同搭建一座桥梁,连接配置源与应用:
┌─────────────────────────────────────────────────────────────────┐
│ Environment 创建过程 │
└─────────────────────────────────────────────────────────────────┘
创建 Environment
│
▼
┌─────────────────────────────┐
│ Servlet环境: │
│ StandardServletEnvironment │
│ │
│ Reactive环境: │
│ StandardReactiveWebEnvironment │
│ │
│ 非Web环境: │
│ StandardEnvironment │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ 配置 PropertySources │
│ ├── CommandLine │
│ ├── SystemProperties │
│ ├── SystemEnvironment │
│ └── application.yml/properties │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ 发布环境准备事件 │
│ ApplicationEnvironmentPreparedEvent │
└─────────────────────────────┘
阶段三:打印Banner
启动Banner是Spring Boot的标志性特征之一,那个醒目的Spring Logo总能让人眼前一亮:
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader :
new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
Banner加载顺序如下(图片优先于文本):
banner.gif/banner.jpg/banner.png(图片文件优先)banner.txt(文本文件)- classpath下的
banner.txt - 默认Spring Boot Banner
阶段四:创建ApplicationContext
根据应用类型,Spring Boot会创建不同的ApplicationContext:
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
| 应用类型 | 上下文类型 |
|---|---|
SERVLET |
AnnotationConfigServletWebServerApplicationContext |
REACTIVE |
AnnotationConfigReactiveWebServerApplicationContext |
NONE |
AnnotationConfigApplicationContext |
阶段五:准备上下文
上下文准备阶段是连接配置与Bean定义的关键环节:
private void prepareContext(DefaultBootstrapContext bootstrapContext,
ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner banner) {
// 设置环境
context.setEnvironment(environment);
// 后置处理上下文
postProcessApplicationContext(context);
// 执行初始化器
applyInitializers(context);
// 发布上下文准备事件
listeners.contextPrepared(context);
// 注册单例Bean
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
if (banner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", banner);
}
// 加载主配置类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 发布上下文加载完成事件
listeners.contextLoaded(context);
}
初始化器执行过程:
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context,
"Unable to call initializer.");
initializer.initialize(context);
}
}
阶段六:刷新上下文
refresh()方法是Spring框架的灵魂所在,定义在 AbstractApplicationContext中。它将Bean定义转化为真正的Bean实例:
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
refresh()方法的十二个步骤,每一步都承载着特定的使命:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 1. 准备刷新上下文
prepareRefresh();
// 2. 获取BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备BeanFactory
prepareBeanFactory(beanFactory);
try {
// 4. 后置处理BeanFactory
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 5. 执行BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册BeanPostProcessor
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 7. 初始化消息源
initMessageSource();
// 8. 初始化事件派发器
initApplicationEventMulticaster();
// 9. 初始化其他特殊Bean
onRefresh();
// 10. 注册监听器
registerListeners();
// 11. 实例化所有非懒加载单例Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
} finally {
resetCommonCaches();
contextRefresh.end();
}
}
}
阶段七:执行Runner
当所有准备工作就绪,Spring Boot会执行用户自定义的Runner:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Runner> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Runner runner : runners) {
if (runner instanceof ApplicationRunner) {
((ApplicationRunner) runner).run(args);
}
if (runner instanceof CommandLineRunner) {
((CommandLineRunner) runner).run(args.getSourceArgs());
}
}
}
五、完整启动时序图
下面的时序图展示了启动过程中各组件之间的协作关系:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│SpringApp │ │Listeners │ │Environment│ │Context │ │BeanFactory│
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │ │
│ starting() │ │ │ │
│───────────────>│ │ │ │
│ │ │ │ │
│ │ createEnvironment() │ │
│────────────────────────────────>│ │ │
│ │ │ │ │
│ │environmentPrepared() │ │
│───────────────>│ │ │ │
│ │ │ │ │
│ │ │ create() │ │
│────────────────────────────────────────────────>│ │
│ │ │ │ │
│ │contextPrepared() │ │
│───────────────>│ │ │ │
│ │ │ │ │
│ │ │ load() │ │
│────────────────────────────────────────────────>│ │
│ │ │ │ │
│ │contextLoaded() │ │ │
│───────────────>│ │ │ │
│ │ │ │ │
│ │ │ │ refresh() │
│────────────────────────────────────────────────>│───────────────>│
│ │ │ │ │
│ │ │ │<───────────────│
│ │ │<───────────────────────────────│
│ │ │ │ │
│ │started() │ │ │
│───────────────>│ │ │ │
│ │ │ │ │
│ │running() │ │ │
│───────────────>│ │ │ │
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
六、自定义启动扩展点
Spring Boot提供了丰富的扩展点,让我们可以在启动流程的关键节点注入自定义逻辑。
6.1 ApplicationContextInitializer
在上下文刷新之前执行,可用于修改ApplicationContext:
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer executed!");
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.addActiveProfile("dev");
}
}
注册方式一:代码注册
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addInitializers(new MyApplicationContextInitializer());
app.run(args);
}
}
注册方式二:配置文件注册(Spring Boot 2.4+推荐使用imports文件)
在 META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports文件中添加:
com.example.MyApplicationContextInitializer
6.2 ApplicationListener
监听应用启动过程中的各种事件,是观察启动流程的绝佳窗口:
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
System.out.println("Application started at: " + event.getTimestamp());
}
}
6.3 CommandLineRunner 和 ApplicationRunner
当应用启动完成后,如果你需要执行一些初始化任务,这两个接口是你的最佳选择:
@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner executed with args: " + Arrays.toString(args));
}
}
@Component
@Order(2)
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner executed");
System.out.println("Option names: " + args.getOptionNames());
System.out.println("Non-option args: " + args.getNonOptionArgs());
}
}
ApplicationRunner与 CommandLineRunner的区别在于:前者提供了更丰富的参数解析能力,可以区分选项参数和非选项参数。
6.4 SmartLifecycle
当你需要在上下文刷新后启动某些后台服务,并在应用关闭时优雅停止,SmartLifecycle是不二之选:
@Component
public class MySmartLifecycle implements SmartLifecycle {
private boolean running = false;
@Override
public void start() {
System.out.println("SmartLifecycle starting...");
running = true;
}
@Override
public void stop() {
System.out.println("SmartLifecycle stopping...");
running = false;
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isAutoStartup() {
return true;
}
}
七、启动流程调试技巧
7.1 启用启动日志
想要一窥启动过程的细节?开启调试日志是最直接的方式:
logging.level.org.springframework.boot=DEBUG
logging.level.org.springframework.context=DEBUG
7.2 启动时间分析
如果你的应用启动缓慢,可以使用 BufferingApplicationStartup来分析每个步骤的耗时:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setApplicationStartup(new BufferingApplicationStartup(10000));
ConfigurableApplicationContext context = app.run(args);
BufferingApplicationStartup startup = (BufferingApplicationStartup)
context.getBeanFactory().getSingleton("springApplicationStartup");
startup.getBufferedSteps().forEach(step -> {
System.out.println(step.getName() + ": " + step.getDuration().toMillis() + "ms");
});
}
}
7.3 使用Spring Boot Actuator
Spring Boot Actuator提供了更专业的启动分析能力:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=startup
启动后访问 /actuator/startup,你将获得一份详尽的启动报告。
八、总结
Spring Boot的启动流程,从创建 SpringApplication到返回一个完整的 ApplicationContext,经历了以下核心步骤:
| 步骤 | 核心操作 | 关键类 |
|---|---|---|
| 1 | 创建SpringApplication | SpringApplication |
| 2 | 推断应用类型 | WebApplicationType |
| 3 | 加载初始化器和监听器 | SpringFactoriesLoader |
| 4 | 准备环境 | ConfigurableEnvironment |
| 5 | 创建ApplicationContext | ApplicationContextFactory |
| 6 | 执行初始化器 | ApplicationContextInitializer |
| 7 | 刷新上下文 | AbstractApplicationContext.refresh() |
| 8 | 执行Runner | CommandLineRunner/ApplicationRunner |
深入理解Spring Boot的启动流程,不仅能帮助我们正确使用各种扩展点,更能让我们在遇到启动问题时快速定位根源,在性能优化时有的放矢。这正是掌握框架原理的价值所在——知其然,更知其所以然。
参考资料:
- Spring Boot官方文档
- Spring Framework官方文档
- Spring Boot源码
全部评论