0%

启动原理

Spring Boot 启动原理详解(基于 2.2.2.RELEASE):从入口到应用就绪的全流程

Spring Boot 的启动过程本质是 “初始化环境→创建上下文→加载 Bean→触发扩展点” 的有序流程,核心入口是 main 方法,通过 SpringApplication 类封装了所有启动逻辑。从 “启动入口→SpringApplication 实例化→run 方法执行→上下文准备与刷新” 四个核心阶段,拆解 Spring Boot 2.2.2.RELEASE 版本的启动原理,帮你理解每个步骤的核心作用。

启动入口:main 方法的 “一键触发”

Spring Boot 应用的启动入口是标注 @SpringBootApplication 的主类中的 main 方法,这行代码是所有启动逻辑的起点:

1
2
3
4
5
6
7
@SpringBootApplication
public class ConsulApp {
public static void main(String[] args) {
// 核心:调用 SpringApplication 的静态 run 方法,传入主类和命令行参数
SpringApplication.run(ConsulApp.class, args);
}
}

静态 run 方法的底层逻辑

进入 SpringApplication.run(Class<?> primarySource, String[] args) 源码,发现它做了两件事:

  1. 实例化 SpringApplication:初始化启动所需的核心组件(如监听器、初始化器);
  2. 调用实例的 run 方法:执行实际的启动流程(环境准备、上下文创建、Bean 加载等)。
1
2
3
4
5
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 1. 实例化 SpringApplication(传入主启动类数组)
// 2. 调用实例的 run 方法(传入命令行参数)
return new SpringApplication(primarySources).run(args);
}

阶段一:SpringApplication 实例化 —— 启动前的 “准备工作”

SpringApplication 的构造方法负责初始化启动所需的核心配置,为后续 run 方法执行打基础。构造方法主要完成 6 件事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1. 设置资源加载器(默认 null,后续会自动初始化)
this.resourceLoader = resourceLoader;
// 2. 校验主启动类不为空(primarySources 是传入的 ConsulApp.class)
Assert.notNull(primarySources, "PrimarySources must not be null");
// 3. 主启动类去重(用 LinkedHashSet 保证顺序和唯一性)
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 4. 判断 Web 应用类型(核心:决定后续创建哪种上下文)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 5. 加载 ApplicationContextInitializer(上下文初始化器)
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 6. 加载 ApplicationListener(应用监听器)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 7. 推断主启动类(找到包含 main 方法的类)
this.mainApplicationClass = deduceMainApplicationClass();
}

关键步骤详解

1. 判断 Web 应用类型(WebApplicationType)

WebApplicationType 决定 Spring Boot 后续创建哪种 ApplicationContext(上下文),通过类路径中是否存在特定类来判断:

  • WebApplicationType.NONE:非 Web 应用(类路径中无 javax.servlet.Servletorg.springframework.web.reactive.DispatcherHandler);
  • WebApplicationType.SERVLET:传统 Servlet Web 应用(存在 javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext);
  • WebApplicationType.REACTIVE:响应式 Web 应用(存在 org.springframework.web.reactive.DispatcherHandler)。

源码依据(WebApplicationType.deduceFromClasspath()):

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
2. 加载初始化器与监听器(SpringFactoriesLoader)

getSpringFactoriesInstances 方法是 Spring Boot 的 “核心加载器”,通过 SpringFactoriesLoader 从类路径下的 META-INF/spring.factories 文件中加载指定接口的实现类:

  • ApplicationContextInitializer:上下文初始化器,用于在上下文刷新前修改配置(如动态添加属性);
  • ApplicationListener:应用监听器,用于监听启动过程中的事件(如 ApplicationStartingEventApplicationEnvironmentPreparedEvent)。

示例:spring.factories 中配置的初始化器和监听器:

1
2
3
4
5
6
7
8
9
# ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer

# ApplicationListener
org.springframework.context.ApplicationListener=\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.embedded.EmbeddedServletContainerListener
3. 推断主启动类(deduceMainApplicationClass)

通过 “构造运行时异常→遍历异常栈” 的巧妙方式,找到包含 main 方法的类(即我们的 ConsulApp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Class<?> deduceMainApplicationClass() {
try {
// 构造运行时异常,获取当前线程的栈轨迹
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 遍历栈轨迹,找到方法名为 "main" 的栈帧
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
// 通过类名加载类,返回主启动类
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException ex) {
// 忽略异常
}
return null;
}

阶段二:执行 run 方法 —— 启动的 “核心流程”

SpringApplication 实例化后,调用 run(String... args) 方法,这是 Spring Boot 启动的核心逻辑。run 方法可拆解为 11 个关键步骤,覆盖 “启动前→启动中→启动后” 全链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public ConfigurableApplicationContext run(String... args) {
// NO1:启动计时(记录整个启动耗时)
StopWatch stopWatch = new StopWatch();
stopWatch.start();

// NO2:初始化上下文和异常报告器(异常时用于生成报告)
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置系统属性 java.awt.headless(默认 true,用于无图形界面环境)
configureHeadlessProperty();

// NO3:加载 SpringApplicationRunListeners 并触发 starting 事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(); // 广播 "应用开始启动" 事件

try {
// NO4:准备应用参数和环境(核心:加载配置、激活环境)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置忽略 BeanInfo(避免加载冗余的 Bean 元信息)
configureIgnoreBeanInfo(environment);

// NO5:打印 Banner(启动时的 Spring 图标,可自定义)
Banner printedBanner = printBanner(environment);

// NO6:创建应用上下文(根据 Web 类型创建对应上下文)
context = createApplicationContext();
// 加载异常报告器(用于启动失败时生成报告)
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

// NO7:准备上下文(初始化器执行、Bean 加载、事件广播)
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

// NO8:刷新上下文(Spring 核心流程,加载 Bean 并初始化)
refreshContext(context);

// NO9:刷新后处理(空方法,预留扩展点)
afterRefresh(context, applicationArguments);
// 停止计时,打印启动耗时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 触发 started 事件(广播 "应用上下文已刷新")
listeners.started(context);

// NO10:执行 Runner(ApplicationRunner 和 CommandLineRunner)
callRunners(context, applicationArguments);

} catch (Throwable ex) {
// 处理启动失败(触发 failed 事件、生成异常报告)
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

// NO11:触发 running 事件(广播 "应用已就绪")
try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}

// 返回初始化完成的上下文
return context;
}

关键步骤详解(核心是 NO4、NO7、NO8、NO10)

1. NO4:准备环境(prepareEnvironment)

prepareEnvironment 是启动的 “配置加载阶段”,负责加载应用的配置(配置文件、系统变量、命令行参数)并激活环境,核心操作:

  • 初始化 ConfigurableEnvironment(根据 Web 类型创建 StandardServletEnvironmentStandardEnvironment);
  • 加载配置源(application.yml/application.properties、命令行参数 --server.port=8081 等);
  • 激活环境(根据 spring.profiles.active 激活对应环境,如 dev/prod);
  • 广播 ApplicationEnvironmentPreparedEvent 事件(通知监听器 “环境已准备完成”)。

源码片段(关键逻辑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 1. 创建环境(根据 Web 类型)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置环境(添加命令行参数、系统变量等 PropertySource)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 激活环境(处理 spring.profiles.active)
ConfigurationPropertySources.attach(environment);
// 4. 广播事件(通知监听器环境已准备)
listeners.environmentPrepared(environment);
// 5. 绑定环境到 SpringApplication(后续使用)
bindToSpringApplication(environment);
return environment;
}
2. NO7:准备上下文(prepareContext)

prepareContext 是 “上下文初始化阶段”,在上下文刷新前完成配置,核心操作:

  • 将环境绑定到上下文(context.setEnvironment(environment));
  • 执行所有 ApplicationContextInitializerinitialize 方法(修改上下文配置);
  • 广播 ApplicationContextInitializedEvent 事件(通知 “上下文已初始化”);
  • 注册特殊 Bean(如 springApplicationArgumentsspringBootBanner);
  • 加载主启动类中的 Bean 定义(扫描 @Component/@Bean 等注解);
  • 广播 ApplicationPreparedEvent 事件(通知 “上下文已准备完成”)。

源码片段(关键逻辑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 1. 绑定环境到上下文
context.setEnvironment(environment);
// 2. 配置上下文(资源加载器、Bean 名称生成器等)
postProcessApplicationContext(context);
// 3. 执行初始化器(ApplicationContextInitializer)
applyInitializers(context);
// 4. 广播事件(上下文已初始化)
listeners.contextPrepared(context);
// 5. 注册特殊 Bean(应用参数、Banner)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 6. 加载主启动类中的 Bean 定义
load(context, sources.toArray(new Object[0]));
// 7. 广播事件(上下文已准备)
listeners.contextLoaded(context);
}
3. NO8:刷新上下文(refreshContext)

refreshContext 是 Spring Boot 启动的 “核心中的核心”,本质是调用 Spring 原生的 AbstractApplicationContext#refresh() 方法 —— 这是 Spring 容器初始化的标准流程,负责:

  • 初始化 BeanFactory(加载 Bean 定义);
  • 执行 BeanFactoryPostProcessor(修改 Bean 定义,如 @Configuration 类解析);
  • 注册 BeanPostProcessor(Bean 初始化前后增强,如 @Autowired 依赖注入);
  • 初始化消息源、事件广播器;
  • 启动嵌入式容器(如 Tomcat,仅 Web 应用);
  • 实例化所有非懒加载的单例 Bean;
  • 广播 ContextRefreshedEvent 事件。

    refresh() 源码已完整覆盖这些步骤, 核心记住:refresh() 完成后,Spring 容器中的所有 Bean 已初始化完成,应用具备对外提供服务的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 设置beanFactory的一些属性
// 添加后置处理器
// 设置忽略的自动装配接口
// 注册一些组件
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {


// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
4. NO10:执行 Runner(callRunners)

callRunners 是启动的 “最后扩展点”,负责执行所有 ApplicationRunnerCommandLineRunner 实现类的 run 方法 —— 这两个接口用于在应用启动后执行初始化逻辑(如加载缓存、初始化数据)。

核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void callRunners(ApplicationContext context, ApplicationArguments applicationArguments) {
List<Object> runners = new ArrayList<>();
// 1. 收集所有 ApplicationRunner 和 CommandLineRunner Bean
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 2. 按 @Order 或 Ordered 接口排序(数字越小越先执行)
AnnotationAwareOrderComparator.sort(runners);
// 3. 遍历执行 run 方法
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, applicationArguments);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, applicationArguments);
}
}
}

阶段三:启动完成与事件广播

run 方法执行到最后,会通过 SpringApplicationRunListeners 广播两个关键事件:

  1. started 事件:上下文刷新完成后触发,表示 “应用已启动但 Runner 未执行”;
  2. running 事件:Runner 执行完成后触发,表示 “应用已完全就绪,可对外提供服务”。

若启动过程中抛出异常,会触发 failed 事件,并通过 SpringBootExceptionReporter 生成异常报告,帮助定位问题(如配置错误、依赖缺失)。

总结:Spring Boot 启动的核心链路

结合上述分析,Spring Boot 2.2.2.RELEASE 的启动流程可概括为 “3 大阶段 + 11 个关键步骤”,用一句话总结:

main 方法入口,先实例化 SpringApplication 完成启动前准备(Web 类型判断、初始化器 / 监听器加载),再执行 run 方法加载环境、创建上下文、刷新容器、执行 Runner,最终广播 “应用就绪” 事件,完成启动。

用流程图更直观展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
main 方法 → SpringApplication 实例化(6步)→ run 方法执行(11步)→ 应用就绪
↓ ↓
1. 资源加载器设置 1. 启动计时
2. 主启动类去重 2. 上下文/异常报告器初始化
3. Web 类型判断 3. 监听器加载与 starting 事件
4. 初始化器加载 4. 环境准备(配置+激活)
5. 监听器加载 5. Banner 打印
6. 主启动类推断 6. 上下文创建
7. 上下文准备(初始化器+Bean加载)
8. 上下文刷新(Spring 核心流程)
9. 计时停止与 started 事件
10. Runner 执行
11. running 事件广播

理解这一流程,能帮你快速定位启动问题(如环境配置错误、Runner 执行失败),也为自定义启动逻辑(如添加初始化器、监听器)提供基础,是深入 Spring Boot 的关键

欢迎关注我的其它发布渠道