技术面:SpringBoot(启动流程、如何优雅停机)
SpringBoot的启动流程
下面的代码是SpingBoot启动类里最基础的代码,SpringBoot的启动的入口就在这里,本文是在SpringBoot3的基础上进行的梳理。
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
在网上能搜到,各种博客和文章来讲解SpingBoot启动流程的,无一不是长篇大论的,把各个步骤,各个阶段执行的代码都贴出来。
确实,内容详尽固然有助于理解,但面试时只需记住主干流程即可,
谁在工作过程中,也不会无故的研究这个启动流程,心里有个大概的了解就可以了,如果实在隔得时间太长了,写代码时又真的会需要了解一下这个启动流程,那么直接在网上搜索,或者直接问AI都是可以的。
所以本次我总结这个启动流程的时候就是尽量的精简步骤,然后进行总结,让大家用自己的话,能给面试官解释清整个流程就行了。
另外还要说明一下,启动流程和实现自动装配不是一回事,启动流程里面是包括自动装配的。如果想了解SpringBoot是如何实现自动装配的,可以看之前写的文章【你来讲一下springboot的启动时的一个自动装配过程吧】
创建SpringBootApplication应用与刷新
首先最核心的一段代码SpringApplication.run(Application.class, args);这段代码会经历一系列的步骤,完成应用的初始化与启动。
这段代码的最后是会调用到SpringApplication.class的ConfigurableApplicationContext方法此方法里面执行的代码为(new SpringApplication(sources)).run(args),因此这个执行过程就分成的两部分,第一部分是new SpringApplication(sources)另一部分是.run(args)
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {return (new SpringApplication(sources)).run(args);
}
new SpringApplication(sources)
这个构造方法的代码如下图所示

弄明白了initialize()方法其实就可以了。
- 添加应用源
this.sources.addAll(Arrays.asList(sources));这段代码的是添加应用源的逻辑,将提供的源(通常是配置类)添加到应用的源列表中。 - 设置 Web 环境
this.deduceWebEnvironment();判断应用是否应该运行在 Web 环境中,这会影响后续的 Web 相关配置。 - 加载初始化器
this.getSpringFactoriesInstances(ApplicationContextInitializer.class)从 spring.factories 文件中加载所有列出的ApplicationContextInitializer实现,并将它们设置到 SpringApplication 实例中,以便在应用上下文的初始化阶段执行它们。 - 设置监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))加载和设置 ApplicationListener实例,以便应用能够响应不同的事件。 - 确定主应用类
this.deduceMainApplicationClass(),这个主应用程序类通常是包含 public static void main(String[]args)方法的类,是启动整个 Spring Boot 应用的入口点。
SpringApplication::run(args)
此方法就是用于启动SpringBoot应用,也是核心流程所在。

1、启动计时器
stopWatch.start();启动计时器,用于记录启动耗时。
2、获取和启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
这一步从spring.factories中解析初始所有的SpringApplicationRunListener 实例,并通知
他们应用的启动过程已经开始。
3、装配环境参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
这一步主要是用来做参数绑定的,prepareEnvironment 方法会加载应用的外部配置。这包括application.properties 或 application.yml 文件中的属性,环境变量,系统属性等。所以,我们自定义的那些参数就是在这一步被绑定的。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {ConfigurableEnvironment environment = this.getOrCreateEnvironment();this.configureEnvironment(environment, applicationArguments.getSourceArgs());listeners.environmentPrepared(environment);if (!this.webEnvironment) {environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary(environment);}return environment;
}
4、创建应用上下文
context = this.createApplicationContext();
到这一步就真的开始启动了,第一步就是先要创建一个Spring的上下文出来,只有有了这个上下文才能进行Bean的加载、配置等工作。
5、准备上下文
这一步很关键了,有很多核心操作在这里。
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
preareContent()方法代码如下,每一步都加上了注释说明。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 设置环境变量context.setEnvironment(environment);// 对应用上下文进行处理,一些自定义处理逻辑也在这里this.postProcessApplicationContext(context);// 应用所有的ApplicationContentInitializerthis.applyInitializers(context);// 通知监听器上下文准备完毕工作已完成。listeners.contextPrepared(context);// 记录启动信息和使用的配置文件信息if (this.logStartupInfo) {this.logStartupInfo(context.getParent() == null);this.logStartupProfileInfo(context);}// 向应用上下文中添加指定的单例Bean
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);}// 加载配置应用源Set<Object> sources = this.getSources();Assert.notEmpty(sources, "Sources must not be empty");this.load(context, sources.toArray(new Object[sources.size()]));// 通知监听器上下文加载已完成listeners.contextLoaded(context);
}
6、刷新上下文
this.refreshContext(context);这一步,是Spring启动的核心步骤了,这一步骤包括了实例化所有的 Bean、设置它们之间的依赖关系以及执行其他的初始化任务。
public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {// 为此刷新操作准备this.prepareRefresh();// 通知子类刷新内部bean工厂ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();// 为刷新做好bean工厂的准备this.prepareBeanFactory(beanFactory);try {// 允许在上下文子类中对bean工厂进行处理this.postProcessBeanFactory(beanFactory);// 调用在上下文中注册为Bean的工厂处理器this.invokeBeanFactoryPostProcessors(beanFactory);// 注册拦截bean创建的bean处理器this.registerBeanPostProcessors(beanFactory);// 初始化此上下文的消息源this.initMessageSource();// 初始化上下文的的时间多播器this.initApplicationEventMulticaster();// 在特定上下文子类中初始化其他特殊Bean(tomcat等内嵌服务器也是在这一步启动)this.onRefresh();// 检测监听器Bean并,注册到容器this.registerListeners();// 实例化所有剩余的(非懒加载)单例this.finishBeanFactoryInitialization(beanFactory);// 最后一步,发布相应的事件this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);}// 销毁已经创建的单利this.destroyBeans();// 重置”激活“标志this.cancelRefresh(var9);// 抛出异常throw var9;} finally {// 在spring的核心中重置常见的内存缓存,因为我们可能不再需要单例Bean的元数据this.resetCommonCaches();}}}
7、执行 CommandLineRunner 和 ApplicationRunner
this.afterRefresh(context, applicationArguments);执行启动参数命令或在应用完全启动后执行一些初始化逻辑,常用于数据初始化。
8、启动完成,返回 ApplicationContext
最终,run() 方法返回一个 ConfigurableApplicationContext,即 Spring 容器上下文,应用正式运行。
总结一下,可以用下面的话来回答面试:
SpringBoot启动流程从SpringApplication.run()开始,通过new SpringApplication()初始化应用源、判断Web环境、加载spring.factories中的初始化器和监听器,并确定主类;随后在run()中准备环境(加载配置)、创建ApplicationContext(如Web应用为ServletWebServerApplicationContext),通过refresh()完成Bean定义加载(含@EnableAutoConfiguration自动配置)、实例化单例Bean并启动内嵌服务器(如Tomcat),最后执行CommandLineRunner等初始化逻辑,最终返回ApplicationContext使应用运行。

SpringBoot如何优雅的停机
优雅停机(Graceful Shutdown)是指在应用停止时,系统能够有序地完成当前正在处理的任务,拒绝新请求,释放资源,从而避免请求中断和数据丢失,确保用户体验和数据一致性。
从SpringBoot 2.3版本开始,框架原生支持优雅停机机制,这是最简单且官方推荐的实现方式。
使用方式,只需要在配置文件里面进行配置即可。
# 启用优雅停机模式
server.shutdown=graceful# 设置等待请求完成的超时时间(默认30秒)
spring.lifecycle.timeout-per-shutdown-phase=30s
工作原理
当应用接收到停止信号(如SIGTERM)时,内嵌Web服务器(Tomcat/Jetty/Undertow)会执行以下步骤:
- 停止接收新请求:内嵌服务器停止接收新的HTTP请求。
- 等待处理中请求:继续处理已接收的请求,直到超时。
- 关闭应用上下文:按顺序销毁所有Bean(执行@PreDestroy方法),停止Web服务器。
- 释放资源:释放数据库连接、线程池等资源。
不同 web服务器,优雅停机行为会有一定的差异。
| 服务器 | 优雅停机行为 |
|---|---|
| Tomcat | 停止接收新连接,等待处理中的请求完成,超时后强制关闭 |
| Jetty | 停止接收新请求,等待活跃请求完成 |
| Undertow | 关闭监听端口,等待活跃请求完成 |
使用SpringBoot Actuator实现优雅停机
使用SpringBoot Actuator实现优雅停机,也就是需要HTTP端点触发优雅停机,需要额外配置:
pom.xml增加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件
# 启用shutdown端点
management.endpoint.shutdown.enabled=true
# 暴露shutdown端点
management.endpoints.web.exposure.include=shutdown
触发优雅停机
curl -X POST http://localhost:8080/actuator/shutdown
Kubernetes环境下的最佳实践
在Kubernetes环境中,优雅停机需要与K8s生命周期配合。
首先,配置文件里面需要进行配置
# 启用优雅停机模式
server.shutdown=graceful
# 设置等待请求完成的超时时间(默认30秒)
spring.lifecycle.timeout-per-shutdown-phase=30s
Kubernetes Deployment配置
terminationGracePeriodSeconds: 30
livenessProbe:httpGet:path: /healthport: 8080initialDelaySeconds: 10periodSeconds: 5
readinessProbe:httpGet:path: /healthport: 8080initialDelaySeconds: 10periodSeconds: 5
优雅停机具体的实现原理,主要有这几步
- 信号接收阶段:通过Runtime.getRuntime().addShutdownHook()注册JVM关闭钩子,或通过Actuator端点接收停机请求
- 应用上下文关闭阶段:发布ContextClosedEvent事件,按顺序销毁所有Bean,停止内嵌Web服务器
- 连接器优雅停止阶段:不同服务器实现方式不同,但都确保等待处理中请求完成
