技術面: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);
// 應用所有的ApplicationContentInitializer
this.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: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
優雅停機具體的實現原理,主要有這幾步
- 信號接收階段:通過Runtime.getRuntime().addShutdownHook()注冊JVM關閉鉤子,或通過Actuator端點接收停機請求
- 應用上下文關閉階段:發布ContextClosedEvent事件,按順序銷毀所有Bean,停止內嵌Web服務器
- 連接器優雅停止階段:不同服務器實現方式不同,但都確保等待處理中請求完成
作者:紀莫
歡迎任何形式的轉載,但請務必注明出處。
限于本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。
歡迎掃描二維碼關注公眾號:Jimoer
文章會同步到公眾號上面,大家一起成長,共同提升技術能力。
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下。
您的鼓勵是博主的最大動力!


浙公網安備 33010602011771號