Java面试-Spring Boot 启动流程源码分析
《Java面试200问》系列之Spring Boot启动流程源码分析,深入解析从main方法到完整应用的全过程。文章通过拆解SpringApplication.run()方法,揭示其两阶段启动机制:首先初始化环境(推断Web类型、加载初始化器和监听器),然后执行核心启动流程。作者结合源码详细讲解了自动配置、事件监听等关键机制,帮助读者理解Spring Boot"约定优于配置"的
👋 欢迎阅读《Java面试200问》系列博客!
🚀大家好,我是Jinkxs,一名热爱Java、深耕技术一线的开发者。在准备和参与了数十场Java面试后,我深知面试不仅是对知识的考察,更是对理解深度与表达能力的综合检验。
✨本系列将带你系统梳理Java核心技术中的高频面试题,从源码原理到实际应用,从常见陷阱到大厂真题,每一篇文章都力求深入浅出、图文并茂,帮助你在求职路上少走弯路,稳拿Offer!
🔍今天我们要聊的是:《Spring Boot 启动流程源码分析》。准备好了吗?Let’s go!
Spring Boot 启动流程源码分析:从 main
到“Hello World”的奇幻漂流
引言:一个 main
方法的“野心”
还记得你第一次写 Spring Boot “Hello World” 时的场景吗?
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
就这么一行 SpringApplication.run()
,一个 Web 服务器就启动了,还自动配置了 Tomcat、Spring MVC、甚至数据库连接池!
你有没有想过:这行代码背后,到底发生了什么?
今天,我们就来一场“代码探险”,深入 Spring Boot 的源码,看看这个看似简单的 main
方法,是如何一步步“点燃”整个应用的。
准备好你的 IDE 和咖啡,我们要“钻”进
SpringApplication.run()
了!
第一站:SpringApplication.run()
—— 启动的“总开关”
1.1 run()
方法源码初探
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
就两行?别急,这就像“薛定谔的猫”——表面平静,内里波涛汹涌。
它做了两件事:
new SpringApplication(primarySource)
:创建一个SpringApplication
实例,准备启动环境.run(args)
:执行真正的启动流程
第二站:new SpringApplication()
—— 初始化“启动器”
2.1 构造方法源码
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 应用类型:REACTIVE, SERVLET, NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 🧩 加载 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 🚨 加载 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 🎯 推断主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
我们来逐个“解剖”:
2.2 推断 Web 应用类型:WebApplicationType.deduceFromClasspath()
Spring Boot 很“聪明”,它会根据类路径下的依赖自动判断应用类型:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)
&& !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
&& !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", null)) {
return WebApplicationType.REACTIVE;
}
if (ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
&& !ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)) {
return WebApplicationType.SERVLET;
}
return WebApplicationType.NONE;
}
类路径存在 | 应用类型 |
---|---|
DispatcherServlet |
SERVLET (传统 Web) |
DispatcherHandler 且无 DispatcherServlet |
REACTIVE (响应式) |
都没有 | NONE (非 Web) |
2.3 加载 ApplicationContextInitializer
这些是在 Spring 容器刷新之前执行的初始化器。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
getSpringFactoriesInstances()
会从 META-INF/spring.factories
中加载:
# spring-boot-autoconfigure/META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
这些初始化器负责:
- 提醒配置错误
- 设置应用 ID
- 代理其他初始化器
- 暴露服务器端口信息
2.4 加载 ApplicationListener
事件监听器,用于响应 Spring 的各种事件(如 ApplicationStartedEvent
、ApplicationReadyEvent
)。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
同样从 spring.factories
加载:
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener
2.5 推断主配置类:deduceMainApplicationClass()
它会遍历调用栈,找到包含 main
方法的类:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException ex) {
// 忽略
}
return null;
}
第三站:.run(args)
—— 启动的“核心引擎”
3.1 run()
方法主流程
public ConfigurableApplicationContext run(String... args) {
// 1. 创建并启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 声明上下文对象
ConfigurableApplicationContext context = null;
// 3. 配置 Headless 模式
configureHeadlessProperty();
// 4. 获取并启动 SpringApplicationRunListeners(主要是 EventPublishingRunListener)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 5. 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 6. 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 7. 配置忽略 BeanInfo
configureIgnoreBeanInfo(environment);
// 8. 打印 Banner
Banner printedBanner = printBanner(environment);
// 9. 创建应用上下文
context = createApplicationContext();
// 10. 准备异常报告器
ExceptionReporters exceptionReporters = getSpringFactoriesInstances(ExceptionReporter.class,
new Class<?>[] { ConfigurableApplicationContext.class }, context);
// 11. 准备应用上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 12. 刷新上下文(核心!)
refreshContext(context);
// 13. 刷新后处理
afterRefresh(context, applicationArguments);
// 14. 停止计时器,输出启动时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 15. 发布应用已启动事件
listeners.started(context);
// 16. 调用 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 17. 发布应用准备就绪事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 18. 返回应用上下文
return context;
}
第四站:prepareEnvironment()
—— 环境准备
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 1. 根据 webApplicationType 创建对应的 Environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置环境(如激活 profile)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 发布环境准备事件
listeners.environmentPrepared(environment);
// 4. 绑定 environment 到 SpringApplication
bindToSpringApplication(environment);
// 5. 如果是 web 环境且未配置无主配置类,则转换环境
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceInitializers());
}
// 6. 配置 PropertySources 的顺序
ConfigurationPropertySources.attach(environment);
return environment;
}
第五站:createApplicationContext()
—— 创建上下文
根据 webApplicationType
创建不同类型的 ApplicationContext
:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context." +
"AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context." +
"AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context." +
"annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException ex) {
throw new IllegalStateException("Unable create a default ApplicationContext", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
第六站:prepareContext()
—— 准备上下文
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 1. 设置环境
context.setEnvironment(environment);
// 2. 执行 ApplicationContextInitializers
postProcessApplicationContext(context);
applyInitializers(context);
// 3. 发布上下文准备事件
listeners.contextPrepared(context);
// 4. 记录启动日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 5. 添加主配置类作为 Bean 定义
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 6. 发布上下文加载事件
listeners.contextLoaded(context);
}
第七站:refreshContext()
—— 刷新上下文(最核心!)
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// 不处理
}
}
}
// 实际调用父类 AbstractApplicationContext 的 refresh()
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新:设置启动时间、激活标志等
prepareRefresh();
// 2. 获取 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备 BeanFactory:设置类加载器、表达式解析器、注册默认的 BeanPostProcessor 等
prepareBeanFactory(beanFactory);
try {
// 4. 子类后处理 BeanFactory
postProcessBeanFactory(beanFactory);
// 5. 执行 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化 MessageSource
initMessageSource();
// 8. 初始化 ApplicationEventMulticaster
initApplicationEventMulticaster();
// 9. 子类特殊初始化
onRefresh();
// 10. 注册监听器
registerListeners();
// 11. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新:发布事件、初始化 LifecycleProcessor
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context refresh", ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
第八站:自动配置的“引爆点”——invokeBeanFactoryPostProcessors
在 refresh()
中,invokeBeanFactoryPostProcessors(beanFactory)
是自动配置的“引爆点”。
它会执行 ConfigurationClassPostProcessor
,而这个处理器会处理 @Configuration
类,最终触发 @EnableAutoConfiguration
。
回忆一下 @EnableAutoConfiguration
的源码:
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
AutoConfigurationImportSelector
会通过 spring.factories
加载所有自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
然后通过 @ConditionalOnXXX
条件注解过滤,最终导入符合条件的配置类。
启动流程全景图
graph TD
A[main()] --> B[new SpringApplication()]
B --> C1[推断 Web 类型]
B --> C2[加载 Initializers]
B --> C3[加载 Listeners]
B --> C4[推断主类]
B --> D[run(args)]
D --> E[启动计时器]
D --> F[获取 RunListeners]
D --> G[准备 Environment]
D --> H[创建 ApplicationContext]
D --> I[准备 Context]
D --> J[refreshContext]
J --> K[prepareRefresh]
J --> L[obtainFreshBeanFactory]
J --> M[prepareBeanFactory]
J --> N[postProcessBeanFactory]
J --> O[invokeBeanFactoryPostProcessors]
O --> P[处理 @EnableAutoConfiguration]
P --> Q[加载 spring.factories]
P --> R[条件过滤 AutoConfig]
J --> S[registerBeanPostProcessors]
J --> T[initMessageSource]
J --> U[initApplicationEventMulticaster]
J --> V[onRefresh]
V --> W[创建内嵌 Web 服务器]
J --> X[registerListeners]
J --> Y[finishBeanFactoryInitialization]
Y --> Z[实例化所有非懒加载 Bean]
J --> AA[finishRefresh]
AA --> AB[发布 ContextRefreshedEvent]
D --> AC[afterRefresh]
D --> AD[callRunners]
D --> AE[发布 running 事件]
D --> AF[返回 ApplicationContext]
面试题与答案
❓ 问:SpringApplication.run()
做了哪些事?
答:
- 创建
SpringApplication
实例,初始化环境、加载监听器和初始化器 - 执行
run()
方法:- 准备环境
- 创建应用上下文
- 准备上下文(加载主配置类)
- 刷新上下文(核心,触发自动配置)
- 调用
ApplicationRunner
/CommandLineRunner
- 返回
ApplicationContext
❓ 问:Spring Boot 如何推断 Web 应用类型?
答:
通过 WebApplicationType.deduceFromClasspath()
方法,检查类路径中是否存在特定类:
- 有
DispatcherServlet
→SERVLET
- 有
DispatcherHandler
且无DispatcherServlet
→REACTIVE
- 都没有 →
NONE
❓ 问:spring.factories
的作用是什么?
答:
它是 Spring Boot 的扩展机制,用于注册自动配置类、监听器、初始化器等。Spring Boot 启动时会加载 META-INF/spring.factories
中的配置,实现“自动发现”。
❓ 问:自动配置是如何生效的?
答:
@EnableAutoConfiguration
通过@Import
导入AutoConfigurationImportSelector
AutoConfigurationImportSelector
从spring.factories
中加载所有自动配置类- 使用
@ConditionalOnXXX
注解过滤,只保留符合条件的配置类 - 这些配置类被加载为
@Configuration
,Spring 容器根据条件创建相应的 Bean
❓ 问:refresh()
方法中最重要的步骤是什么?
答:invokeBeanFactoryPostProcessors(beanFactory)
最关键,因为它触发了 ConfigurationClassPostProcessor
,进而处理 @EnableAutoConfiguration
,加载自动配置类,是“自动配置”的起点。
❓ 问:如何自定义启动过程?
答:
- 实现
ApplicationContextInitializer
并在spring.factories
中注册 - 实现
ApplicationListener
监听启动事件 - 使用
SpringApplicationRunListener
监控启动全过程 - 在
main
方法中自定义SpringApplication
实例
总结
阶段 | 关键动作 |
---|---|
初始化 | 推断类型、加载监听器/初始化器 |
run() | 计时、准备环境、创建上下文 |
prepareContext | 设置环境、执行初始化器、加载主类 |
refreshContext | 刷新容器、执行自动配置、实例化 Bean |
启动完成 | 调用 Runner、发布事件 |
Spring Boot 的启动流程,就像一场精心编排的“交响乐”,从 main
方法开始,一步步构建环境、加载配置、启动容器,最终让应用“活”起来。
理解这个流程,不仅能让你在面试中游刃有余,更能让你在实际开发中“知其然,更知其所以然”。
下次当你看到
SpringApplication.run()
时,不妨在心里默念:“启动,开始!”
🎯 总结一下:
本文深入探讨了《Spring Boot 启动流程源码分析》,从原理到实践,解析了面试中常见的考察点和易错陷阱。掌握这些内容,不仅能应对面试官的连环追问,更能提升你在实际开发中的技术判断力。
🔗 下期预告:我们将继续深入Java面试核心,带你解锁《Spring Boot 中的 Starter 原理与自定义 Starter》 的关键知识点,记得关注不迷路!
💬 互动时间:你在面试中遇到过类似问题吗?或者对本文内容有疑问?欢迎在评论区留言交流,我会一一回复!
如果你觉得这篇文章对你有帮助,别忘了 点赞 + 收藏 + 转发,让更多小伙伴一起进步!我们下一篇见 👋
更多推荐
所有评论(0)