Spring Boot啟動流程示例詳解
1. Spring Boot啟動流程概述
1.1 Spring Boot的核心特性
Spring Boot是Spring官方推出的一個快速構建獨立運行、生產級別Spring應用的框架,它的核心目標是簡化Spring應用的搭建與部署。
核心特性主要包括:
- 內嵌Web容器
- 內置Tomcat、Jetty、Undertow,無需單獨部署WAR包到外部容器。只需運行
java -jar即可啟動。 - 自動配置(Auto Configuration)
- 通過
@EnableAutoConfiguration結合條件化裝配(Conditional注解)自動為應用注冊Bean,大幅減少xml或Java配置的編寫量。 - 獨立運行(Standalone)
- 應用可以以一個獨立的JAR包形式運行,避免復雜的服務器配置。
- 起步依賴(Starter Dependencies)
- 提供
spring-boot-starter-*依賴包,按需引入常用功能,減少手動管理依賴的復雜度。 - 生產環(huán)境特性
- 提供
Actuator監(jiān)控、健康檢查、環(huán)境信息、度量指標等。
類比:可以把Spring Boot想象成“開箱即用的開發(fā)平臺”,你不需要自己組裝各種組件,它已經幫你準備好了一整套“工具箱”和“默認配置”。
1.2 啟動流程整體架構(文字描述)
Spring Boot啟動的核心流程可以抽象成五個階段:
- 初始化SpringApplication
- 解析啟動類(
@SpringBootApplication標注的類) - 推斷應用類型(
WebApplicationType) - 加載初始化器(
ApplicationContextInitializer) - 加載監(jiān)聽器(
ApplicationListener)
- 解析啟動類(
- 準備運行環(huán)境(Environment)
- 創(chuàng)建并配置
Environment對象 - 加載外部配置(命令行參數(shù)、配置文件、系統(tǒng)屬性等)
- 配置轉換器(
PropertySource->Environment)
- 創(chuàng)建并配置
- 創(chuàng)建并刷新ApplicationContext
- 根據(jù)應用類型創(chuàng)建合適的
ApplicationContext(Servlet、Reactive或普通應用) - 注冊Bean定義、執(zhí)行
BeanFactoryPostProcessor和BeanPostProcessor - 啟動內嵌Web容器(如Tomcat)
- 根據(jù)應用類型創(chuàng)建合適的
- 自動配置與Bean加載
- 解析
@EnableAutoConfiguration加載自動配置類 - 條件化裝配判斷是否加載特定Bean
- 初始化所有單例Bean
- 解析
- 完成啟動并執(zhí)行回調
- 發(fā)布
ApplicationReadyEvent事件 - 執(zhí)行
CommandLineRunner和ApplicationRunner
- 發(fā)布
文字版的啟動時序:
SpringApplication實例化 -> 推斷Web類型 -> 加載監(jiān)聽器 -> 創(chuàng)建Environment -> 加載配置 -> 創(chuàng)建ApplicationContext -> 自動配置 -> 啟動容器 -> 應用就緒
1.3 Spring Boot 2.x 與 3.x 啟動機制差異
在Spring Boot 3.x(基于Spring Framework 6)中,啟動流程的總體框架與2.x一致,但有一些細節(jié)變化:
- WebApplicationType推斷優(yōu)化
- Spring Boot 3.x在推斷應用類型時,對
org.springframework.web.reactive.DispatcherHandler等類的檢測進行了微調,更精確地區(qū)分Servlet與Reactive應用。
- Spring Boot 3.x在推斷應用類型時,對
- SpringFactoriesLoader升級
- 2.x版本讀取
META-INF/spring.factories文件使用Properties加載,3.x版本引入了新的API(SpringFactoriesLoader.forDefaultResourceLocation()),并且支持META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,提高了加載性能。
- 2.x版本讀取
- BootstrapContext增強
- 3.x版本中
BootstrapContext在啟動階段的使用更靈活,可以在應用啟動前注冊額外的組件。
- 3.x版本中
- Jakarta命名空間遷移
- 所有與Servlet相關的類名由
javax.*遷移到jakarta.*,這在3.x升級時需要特別注意。
- 所有與Servlet相關的類名由
示例:最簡單的Spring Boot引導類
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @SpringBootApplication 是三個注解的組合:
* - @Configuration:表示該類是一個配置類
* - @EnableAutoConfiguration:開啟自動配置
* - @ComponentScan:開啟包掃描
*/
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// run方法會啟動整個Spring Boot應用
SpringApplication.run(DemoApplication.class, args);
}
}運行上面的程序,你會在控制臺看到類似輸出(簡化版):
:: Spring Boot :: (v3.2.0)
2025-08-11 10:15:00 INFO --- Starting DemoApplication on localhost with PID 12345
2025-08-11 10:15:01 INFO --- Tomcat started on port(s): 8080 (http)
2025-08-11 10:15:01 INFO --- Started DemoApplication in 1.234 seconds
2. Spring Boot啟動的核心階段
2.1 SpringApplication初始化階段
當你調用:
SpringApplication.run(DemoApplication.class, args);
實際上會先執(zhí)行 new SpringApplication(primarySources...) 構造方法,再執(zhí)行 .run(args) 方法。
源碼入口(Spring Boot 2.x / 3.x)
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推斷應用類型(Servlet / Reactive / None)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 從 spring.factories 加載 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 從 spring.factories 加載 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推斷并設置 main 方法所在類
this.mainApplicationClass = deduceMainApplicationClass();
}幾個關鍵點:
- 推斷 WebApplicationType
- 如果classpath中存在
DispatcherServlet,就認為是Servlet類型應用; - 如果存在
DispatcherHandler,就認為是Reactive類型應用; - 否則是普通的
NONE類型(無Web環(huán)境)。
- 如果classpath中存在
- 加載初始化器(Initializers)
- 通過
SpringFactoriesLoader掃描META-INF/spring.factories,自動實例化所有ApplicationContextInitializer實現(xiàn)類。
- 通過
- 加載監(jiān)聽器(Listeners)
- 同樣用
SpringFactoriesLoader機制加載ApplicationListener實現(xiàn)類,用于監(jiān)聽應用啟動過程的事件。
- 同樣用
- 推斷Main類
- 遍歷當前線程的調用棧,找到第一個包含
main方法的類。
- 遍歷當前線程的調用棧,找到第一個包含
示例:自定義ApplicationContextInitializer
public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
System.out.println("MyInitializer: 應用上下文初始化中...");
}
}
resources/META-INF/spring.factories:
org.springframework.context.ApplicationContextInitializer=\ com.example.demo.MyInitializer
啟動時會先輸出:
MyInitializer: 應用上下文初始化中...
說明在SpringApplication構造時已成功加載該初始化器。
2.2 SpringApplication.run()執(zhí)行過程全解析
SpringApplication.run() 是整個啟動的核心,下面是簡化后的源碼執(zhí)行順序(以3.x為例):
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 啟動監(jiān)聽器集合
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
// 初始化引導上下文(BootstrapContext)
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 創(chuàng)建并配置 Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, args);
// 打印 Banner
Banner printedBanner = printBanner(environment);
// 創(chuàng)建 ApplicationContext
context = createApplicationContext();
// 準備上下文
prepareContext(bootstrapContext, context, environment, listeners, args, printedBanner);
// 刷新上下文(加載Bean、啟動內嵌容器)
refreshContext(context);
// 執(zhí)行 CommandLineRunner、ApplicationRunner
afterRefresh(context, args);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
listeners.running(context);
return context;
}分步驟解析:
- StopWatch計時器
- 用于記錄啟動耗時,最終會在日志輸出
Started Xxx in 2.345 seconds。
- 用于記錄啟動耗時,最終會在日志輸出
- 啟動監(jiān)聽器(SpringApplicationRunListeners)
- 觸發(fā)
ApplicationStartingEvent事件,可以在這里做日志或系統(tǒng)初始化。
- 觸發(fā)
- 創(chuàng)建BootstrapContext
- 啟動早期用于注冊額外組件,如配置加載器(Spring Cloud 會用到)。
- 創(chuàng)建并準備Environment
- 加載外部配置(命令行、配置文件、環(huán)境變量)。
- 設置
PropertySource的加載順序。
- 打印Banner
- 控制臺輸出Spring Boot ASCII藝術字,可以自定義。
- 創(chuàng)建ApplicationContext
- 根據(jù)Web類型創(chuàng)建
AnnotationConfigServletWebServerApplicationContext或AnnotationConfigApplicationContext。
- 根據(jù)Web類型創(chuàng)建
- prepareContext
- 注冊初始化器(Initializer)。
- 注冊監(jiān)聽器(Listener)。
- 注冊主配置類(啟動類)。
- refreshContext
- 調用Spring核心的
refresh()方法,完成Bean的加載、依賴注入、事件注冊等。
- 調用Spring核心的
- 執(zhí)行Runner回調
CommandLineRunner和ApplicationRunner用于啟動完成后的業(yè)務處理。
- 觸發(fā)應用就緒事件
- 發(fā)布
ApplicationReadyEvent,代表應用完全啟動并可對外提供服務。
- 發(fā)布
示例:監(jiān)聽啟動事件
@Component
public class MyStartupListener implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
System.out.println("應用啟動中:執(zhí)行早期初始化邏輯...");
}
}2.3 WebApplicationType推斷機制
源碼(簡化):
public static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(REACTIVE_DISPATCHER, null) && !ClassUtils.isPresent(DISPATCHER_SERVLET, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}判斷邏輯:
- 如果存在
org.springframework.web.reactive.DispatcherHandler且不存在DispatcherServlet→ REACTIVE - 如果缺少Servlet必要類 → NONE
- 其他情況默認SERVLET
?? 小結:
SpringApplication構造階段主要是推斷類型、加載初始化器、監(jiān)聽器。run()方法則是準備環(huán)境、創(chuàng)建上下文、刷新Bean、執(zhí)行回調的全過程。- Web類型推斷保證了Spring Boot能自動適配不同類型的應用(Servlet、Reactive、None)。
3. 環(huán)境準備與配置加載
3.1 Environment對象的創(chuàng)建與類型
在 Spring Boot 啟動時,會根據(jù)應用類型(WebApplicationType)來創(chuàng)建不同的 Environment 實現(xiàn)類:
| 應用類型 | Environment實現(xiàn)類 |
|---|---|
| SERVLET | StandardServletEnvironment |
| REACTIVE | StandardReactiveWebEnvironment |
| NONE | StandardEnvironment |
源碼位置(SpringApplication.prepareEnvironment):
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}Environment 作用:
- 存放應用的配置源(
PropertySource)。 - 提供配置值的統(tǒng)一獲取接口(
getProperty)。 - 提供 profile 管理(
setActiveProfiles)。
小示例:獲取Environment對象
@Component
public class EnvPrinter implements CommandLineRunner {
@Autowired
private Environment environment;
@Override
public void run(String... args) {
System.out.println("當前激活的profile: " + Arrays.toString(environment.getActiveProfiles()));
System.out.println("server.port=" + environment.getProperty("server.port"));
}
}運行時會輸出配置文件或命令行參數(shù)中對應的值。
3.2 配置源加載順序與優(yōu)先級
Spring Boot 的配置加載是有優(yōu)先級順序的,后加載的會覆蓋先加載的。
官方的 優(yōu)先級順序(從高到低):
- 命令行參數(shù)
--server.port=9000
- Java系統(tǒng)屬性
System.setProperty("server.port", "9000")
- 操作系統(tǒng)環(huán)境變量
application-{profile}.properties(當前激活的 profile 配置)application.properties/application.yml
- 打包在 jar 外部的配置文件(優(yōu)先于 jar 內部配置文件)
- 打包在 jar 內部的配置文件
- 默認配置(代碼中
SpringApplication.setDefaultProperties()設置)
- 默認配置(代碼中
命令行參數(shù)覆蓋示例
假設 application.properties 中配置:
server.port=8080
運行:
java -jar demo.jar --server.port=9001
最終生效端口是 9001,因為命令行參數(shù)優(yōu)先級最高。
3.3 application.properties 與 application.yml 加載規(guī)則
Spring Boot 支持 .properties 和 .yml 兩種格式,加載順序(同一位置)是:
- 先加載
.properties - 再加載
.yml
并且,配置文件可以放在多個位置:
classpath:/classpath:/config/file:./file:./config/
優(yōu)先級順序(從高到低):
file:./config/file:./classpath:/config/classpath:/
3.4 命令行參數(shù)、系統(tǒng)屬性覆蓋規(guī)則
Spring Boot 會自動將命令行參數(shù)解析成 PropertySource 并加入 Environment,它們的優(yōu)先級高于配置文件。
源碼位置:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
new CommandLinePropertySource<>("commandLineArgs", args);
}命令行參數(shù)會被解析成 key-value 形式,覆蓋之前加載的同名配置。
3.5 @Value 與 @ConfigurationProperties 獲取配置
@Value
直接從 Environment 中取值:
@Value("${server.port}")
private int port;
@ConfigurationProperties
批量綁定配置到 Java Bean:
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppConfig {
private String name;
private int timeout;
// getters and setters
}application.properties:
myapp.name=DemoApp myapp.timeout=30
這樣 MyAppConfig 會自動綁定配置值。
?? 小結:
Environment是配置的“中央倉庫”。- 配置源加載是分階段進行的,且有明確的優(yōu)先級。
- 命令行參數(shù)幾乎總是能覆蓋所有配置文件值。
@Value適合單個配置值,@ConfigurationProperties適合批量綁定。
4. ApplicationContext的創(chuàng)建與刷新
4.1 ApplicationContext 的類型選擇
在 SpringApplication.createApplicationContext() 中,會根據(jù) WebApplicationType 選擇不同的 ApplicationContext 實現(xiàn):
| 應用類型 | ApplicationContext 實現(xiàn)類 |
|---|---|
| SERVLET | AnnotationConfigServletWebServerApplicationContext |
| REACTIVE | AnnotationConfigReactiveWebServerApplicationContext |
| NONE | AnnotationConfigApplicationContext |
源碼(簡化):
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 to create ApplicationContext", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
重點:
- SERVLET 類型不僅是 IoC 容器,還會管理內嵌的 Servlet Web 服務器(Tomcat/Jetty/Undertow)。
- REACTIVE 類型則會啟動 Netty 作為反應式 Web 服務器。
- NONE 類型適用于純后端任務(無Web功能)。
4.2 refreshContext() 核心邏輯解析
refreshContext() 會調用 Spring Framework 的 AbstractApplicationContext.refresh(),這是 IoC 容器生命周期的核心方法。
簡化執(zhí)行步驟(以 Servlet 應用為例):
- 準備刷新上下文
- 初始化屬性源(PropertySources)
- 驗證必需的屬性是否存在
- 獲取并準備 BeanFactory
- 創(chuàng)建
DefaultListableBeanFactory
- 創(chuàng)建
- 執(zhí)行 BeanFactoryPostProcessor
- 在 Bean 定義加載后、實例化前執(zhí)行
- 注冊 BeanPostProcessor
- 在 Bean 實例化前后進行增強(如 @Autowired 注入、AOP 代理)
- 初始化消息源(MessageSource)
- 用于國際化
- 初始化事件廣播器(ApplicationEventMulticaster)
- 創(chuàng)建內嵌 Web 服務器(ServletWebServerFactory)并啟動
- 實例化剩余的單例 Bean
- 完成刷新
- 發(fā)布
ContextRefreshedEvent
- 發(fā)布
4.3 BeanFactoryPostProcessor 與 BeanPostProcessor 執(zhí)行順序
很多擴展功能(比如 MyBatis、Spring Cloud)都是依賴這兩個接口來修改 Bean 定義或 Bean 實例的。
執(zhí)行順序:
- BeanFactoryPostProcessor
- 作用:修改 Bean 定義元數(shù)據(jù)(
BeanDefinition) - 運行時機:Bean 實例化 之前
- 常見實現(xiàn):
ConfigurationClassPostProcessor(處理 @Configuration、@Bean)
- 作用:修改 Bean 定義元數(shù)據(jù)(
- BeanPostProcessor
- 作用:增強 Bean 實例(代理、依賴注入等)
- 運行時機:Bean 實例化 前后
- 常見實現(xiàn):
AutowiredAnnotationBeanPostProcessor(處理 @Autowired)、AopProxyCreator
示例:自定義 BeanPostProcessor
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (beanName.equals("myService")) {
System.out.println("Before init: " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (beanName.equals("myService")) {
System.out.println("After init: " + beanName);
}
return bean;
}
}4.4 BootstrapContext 的作用
在 Spring Boot 2.4 之后引入 BootstrapContext,它是應用啟動早期的一個輕量級上下文,用于注冊啟動前所需的組件,典型用途是:
- 注冊配置加載器(Spring Cloud Config 使用)
- 注冊早期的監(jiān)控或指標收集組件
源碼片段:
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ApplicationEnvironmentPreparedEvent event =
new ApplicationEnvironmentPreparedEvent(this, args, environment);
listeners.environmentPrepared(bootstrapContext, environment);
?? 小結:
- ApplicationContext 是整個應用的核心容器,負責 Bean 生命周期、事件、資源管理。
- refresh() 是啟動過程中 Bean 加載的核心方法。
- BeanFactoryPostProcessor 改定義,BeanPostProcessor 改實例。
- BootstrapContext 在早期階段提供了擴展能力。
5. 自動配置與條件化裝配
Spring Boot最大的亮點之一,就是它的自動配置(Auto-Configuration)機制——你只需要引入依賴,不用手動配置,相關的Bean就會自動幫你注冊好。
這一章我們會拆開它的底層原理,逐步分析:
5.1 @EnableAutoConfiguration 原理
在Spring Boot應用的啟動類中,我們經常會看到:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication 是一個組合注解,內部包含了:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)}
)
public @interface SpringBootApplication {
// ...
}
核心就是 @EnableAutoConfiguration。
源碼入口
@EnableAutoConfiguration 本身也只是一個注解,它通過 @Import 引入了 AutoConfigurationImportSelector:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
重點來了:
@Import會把AutoConfigurationImportSelector這個類加入到容器啟動過程中。- 這個類會掃描所有
META-INF/spring.factories中聲明的自動配置類。
5.2 SpringFactoriesLoader 與 META-INF/spring.factories
SpringFactoriesLoader 是一個幫助類,負責加載 META-INF/spring.factories 文件中定義的類名列表。
典型的 spring.factories 文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
工作流程:
SpringFactoriesLoader.loadFactoryNames()會掃描所有依賴包下的META-INF/spring.factories文件。- 讀取 key 為
org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置值。 - 把這些類(通常以
xxxAutoConfiguration結尾)交給Spring容器去加載。
源碼關鍵點(SpringFactoriesLoader)
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
- 這里的
factoryType就是EnableAutoConfiguration.class - 最終得到一個自動配置類的全限定名列表
5.3 條件化注解(@ConditionalXXX)
自動配置類并不是無條件生效的,它們會結合一系列條件注解判斷當前環(huán)境是否滿足,再決定是否注冊Bean。
常見的條件注解:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass | 當類路徑存在指定類時生效 |
@ConditionalOnMissingClass | 當類路徑不存在指定類時生效 |
@ConditionalOnBean | 當容器中存在指定Bean時生效 |
@ConditionalOnMissingBean | 當容器中不存在指定Bean時生效 |
@ConditionalOnProperty | 當配置文件中存在某個屬性并且值匹配時生效 |
@ConditionalOnWebApplication | 當應用是Web類型時生效 |
@ConditionalOnNotWebApplication | 當應用不是Web類型時生效 |
@ConditionalOnExpression | 根據(jù)SpEL表達式結果決定是否生效 |
例子(JacksonAutoConfiguration):
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@EnableConfigurationProperties(JacksonProperties.class)
public class JacksonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}解釋:
- 如果Classpath中有
ObjectMapper類(Jackson庫),則配置生效。 - 如果容器中沒有
ObjectMapper,則自動注冊一個。
5.4 自定義自動配置類實戰(zhàn)
假設我們想在Spring Boot啟動時,自動配置一個 HelloService:
1. 編寫服務類
public class HelloService {
public String sayHello(String name) {
return "Hello, " + name;
}
}
2. 編寫自動配置類
@Configuration
@ConditionalOnClass(HelloService.class)
public class HelloServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HelloService helloService() {
return new HelloService();
}
}3. 配置 META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.demo.config.HelloServiceAutoConfiguration
4. 引入依賴后直接使用
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
public String hello(String name) {
return helloService.sayHello(name);
}
}無需額外配置,訪問 /hello?name=Boot 即可看到自動配置效果。
5.5 條件注解(@ConditionalXXX)的源碼執(zhí)行流程
Spring Boot 的自動配置類之所以能“按需生效”,核心就是依賴 Spring Framework 提供的 條件化裝配機制,也就是 @Conditional。
5.5.1 條件注解的繼承關系
所有 @ConditionalOnXxx 注解,最終都是 @Conditional 的語法糖。
舉個例子:
@ConditionalOnClass(ObjectMapper.class)
展開看源碼:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
可以看到:
- 它其實就是
@Conditional,只不過指定了一個Condition實現(xiàn)類:OnClassCondition。 - 也就是說,
@ConditionalOnClass-> OnClassCondition -> 判斷classpath是否存在指定類。
5.5.2 條件評估流程
條件注解是在 Spring 容器啟動階段,由 ConfigurationClassPostProcessor 解析 @Configuration 類時進行評估的。
流程大致是:
- 掃描到自動配置類
ConfigurationClassParser解析自動配置類上的注解。
- 遇到 @Conditional
- 調用
ConditionEvaluator.shouldSkip()方法。
- 調用
- 找到對應 Condition 實現(xiàn)類
- 比如
OnClassCondition,會去執(zhí)行matches()方法。
- 比如
- 返回 true/false
true表示條件滿足 -> 該配置類或方法會注冊到容器false表示條件不滿足 -> 直接跳過
源碼片段(ConditionEvaluator)
public boolean shouldSkip(AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata.isAnnotated(Conditional.class.getName())) {
List<Condition> conditions = getConditionClasses(metadata);
for (Condition condition : conditions) {
if (!condition.matches(context, metadata)) {
return true; // 跳過
}
}
}
return false;
}
重點:
matches()方法是判斷條件是否成立的地方。- 每個
@ConditionalOnXxx注解都有自己的 Condition 實現(xiàn)類。
5.5.3 常見 Condition 實現(xiàn)類
| 條件注解 | 對應 Condition 類 | 作用 |
|---|---|---|
@ConditionalOnClass | OnClassCondition | 檢查類路徑是否存在指定類 |
@ConditionalOnMissingClass | OnClassCondition | 檢查類路徑是否不存在指定類 |
@ConditionalOnBean | OnBeanCondition | 檢查容器中是否存在指定 Bean |
@ConditionalOnMissingBean | OnBeanCondition | 檢查容器中是否不存在指定 Bean |
@ConditionalOnProperty | OnPropertyCondition | 檢查配置文件中屬性是否滿足 |
@ConditionalOnWebApplication | OnWebApplicationCondition | 檢查應用是否是 Web 環(huán)境 |
@ConditionalOnExpression | OnExpressionCondition | 執(zhí)行 SpEL 表達式并判斷結果 |
5.5.4 執(zhí)行順序與調試技巧
- 條件判斷會在 BeanDefinition 注冊階段執(zhí)行,而不是Bean實例化階段。
- 你可以在
Condition.matches()方法里打斷點,啟動項目,就能看到哪些條件返回 false,從而排查自動配置沒生效的原因。 - Spring Boot 提供了一個
--debug啟動參數(shù),可以打印出條件評估報告。
命令行啟動
java -jar demo.jar --debug
部分輸出
=========================
CONDITIONS EVALUATION REPORT
=========================
Positive matches:
-----------------
JacksonAutoConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
Negative matches:
-----------------
DataSourceAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.sql.DataSource' (OnClassCondition)這樣你就能非常清楚地知道某個自動配置類為什么沒生效。
5.6 自動配置的優(yōu)先級與覆蓋規(guī)則
Spring Boot 設計的自動配置是低優(yōu)先級的,這樣開發(fā)者可以隨時覆蓋默認配置。
5.6.1 自動配置的核心原則
- 用戶優(yōu)先(開發(fā)者自己配置的 Bean 優(yōu)先于 Spring Boot 提供的 Bean)
- 條件觸發(fā)(只有在條件成立時,自動配置才會生效)
- 可覆蓋可禁用(你可以通過配置文件或注解控制自動配置是否啟用)
5.6.2 自動配置的低優(yōu)先級實現(xiàn)方式
1.@ConditionalOnMissingBean
- 大多數(shù)自動配置類在注冊 Bean 時都會加上這個條件。
- 表示:如果容器中已經有這個 Bean,就不再創(chuàng)建默認的 Bean。
示例:
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
意思是:
- 如果你自己在配置類里聲明了
ObjectMapperBean,那么 Boot 的這個方法不會執(zhí)行。
2.@AutoConfigureBefore/@AutoConfigureAfter
- 用來指定自動配置類之間的順序。
- 比如:
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class MyCustomJsonConfig { ... }
這樣你的配置會在 JacksonAutoConfiguration 之前執(zhí)行。
3.@Primary
- 如果你想讓多個同類型 Bean 中某一個優(yōu)先被注入,可以加上
@Primary。 - 這不是自動配置特有的,但常用于覆蓋默認 Bean。
4.spring.main.allow-bean-definition-overriding
- Spring Boot 2.1 以后默認不允許 Bean 覆蓋,如果你聲明了和默認配置同名的 Bean 會報錯。
- 解決辦法:
spring:
main:
allow-bean-definition-overriding: true這樣你的 Bean 會覆蓋默認 Bean。
5.6.3 禁用某個自動配置
有三種常用方式:
方式 1:@SpringBootApplication(exclude = ...)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MyApp {}
方式 2:配置文件
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration方式 3:META-INF/spring.factories
- 這種是你自己寫 starter 時用的,普通項目一般不直接操作。
5.6.4 調試 Bean 覆蓋問題
- 啟動加
--debug看條件評估報告。 - 使用
ApplicationContext打印所有 Bean:
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
如果報 BeanDefinitionOverrideException,先確認是否是自己命名沖突,必要時允許覆蓋。
6. Spring Boot啟動流程的擴展點
Spring Boot的設計理念之一是高度的可擴展性。在啟動過程中,Spring Boot提供了豐富的擴展點,允許開發(fā)者在不同階段插入自定義邏輯,滿足多樣化的業(yè)務需求。這一章將詳細介紹Spring Boot啟動流程中常用的擴展點,包括SpringApplicationRunListener、ApplicationListener以及SpringApplicationBuilder的鏈式啟動配置。
6.1 SpringApplicationRunListener —— 啟動事件監(jiān)聽器
6.1.1 作用與觸發(fā)時機
SpringApplicationRunListener接口用于監(jiān)聽Spring Boot啟動流程的各個關鍵階段。它的核心目的是在啟動流程的不同時間點觸發(fā)回調,幫助開發(fā)者插入自定義操作或日志打印等輔助功能。
Spring Boot啟動時會依次調用SpringApplicationRunListener中的如下事件方法:
starting():啟動初始化,Spring Boot啟動的第一個階段。environmentPrepared(ConfigurableEnvironment environment):環(huán)境準備完成,配置已加載。contextPrepared(ConfigurableApplicationContext context):ApplicationContext創(chuàng)建完成但尚未刷新。contextLoaded(ConfigurableApplicationContext context):ApplicationContext加載完成。started(ConfigurableApplicationContext context):ApplicationContext刷新完成。running(ConfigurableApplicationContext context):應用準備就緒,進入運行狀態(tài)。failed(ConfigurableApplicationContext context, Throwable exception):啟動失敗回調。
6.1.2 Spring Boot內部實現(xiàn)及自動加載
Spring Boot通過META-INF/spring.factories文件自動加載所有注冊的SpringApplicationRunListener實現(xiàn)。常見的內置實現(xiàn)包括:
EventPublishingRunListener:負責將啟動事件發(fā)布到Spring事件機制。ClasspathLoggingApplicationListener:啟動時打印類路徑信息。StartupInfoLogger:打印啟動信息統(tǒng)計。
自動加載示例(spring.factories文件):
org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener
6.1.3 自定義 SpringApplicationRunListener
開發(fā)者可以通過實現(xiàn)SpringApplicationRunListener接口來自定義啟動事件監(jiān)聽器。實現(xiàn)步驟:
- 實現(xiàn)接口并提供帶
SpringApplication和String[] args構造函數(shù)。 - 在
META-INF/spring.factories注冊監(jiān)聽器類。
示例代碼:
public class MyRunListener implements SpringApplicationRunListener {
public MyRunListener(SpringApplication app, String[] args) {
// 構造器,必須包含這兩個參數(shù)
}
@Override
public void starting() {
System.out.println("應用開始啟動");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("環(huán)境已準備好");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("上下文已準備好");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("上下文已加載");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("應用已啟動");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("應用正在運行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("應用啟動失敗,異常信息:" + exception.getMessage());
}
}注冊監(jiān)聽器:
org.springframework.boot.SpringApplicationRunListener=com.example.MyRunListener
6.2 ApplicationListener —— Spring事件監(jiān)聽器
6.2.1 作用與應用場景
ApplicationListener是Spring框架的核心事件監(jiān)聽接口,支持監(jiān)聽包括啟動事件在內的所有Spring事件。它基于Spring的事件發(fā)布機制,允許開發(fā)者對特定事件做出響應。
相比SpringApplicationRunListener,ApplicationListener更通用,不僅限于啟動階段事件,還可監(jiān)聽應用運行過程中的各種事件。
6.2.2 常見啟動事件類型
| 事件類 | 觸發(fā)時機 |
|---|---|
ApplicationStartingEvent | Spring Boot啟動最開始時 |
ApplicationEnvironmentPreparedEvent | 環(huán)境準備完成時 |
ApplicationPreparedEvent | ApplicationContext創(chuàng)建完畢,準備刷新 |
ApplicationStartedEvent | ApplicationContext刷新成功 |
ApplicationReadyEvent | 應用啟動完成,準備接收請求 |
ApplicationFailedEvent | 應用啟動失敗 |
6.2.3 自定義ApplicationListener示例
@Component
public class MyReadyListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("應用已準備就緒,執(zhí)行自定義初始化操作...");
// 例如啟動緩存預熱、消息監(jiān)聽器等
}
}6.3 SpringApplicationBuilder —— 鏈式啟動配置工具
6.3.1 設計理念與優(yōu)勢
SpringApplicationBuilder是構建SpringApplication實例的鏈式構建器,支持更靈活、優(yōu)雅的啟動配置,適合復雜啟動場景。
使用SpringApplicationBuilder,可以逐步疊加各種配置,如初始化器、監(jiān)聽器、激活Profile等,并最后調用run()方法啟動應用。
6.3.2 常用API與示例
sources(Class<?>... sources):設置主配置類或額外配置類。initializers(ApplicationContextInitializer<?>... initializers):添加上下文初始化器。listeners(ApplicationListener<?>... listeners):添加事件監(jiān)聽器。profiles(String... profiles):激活指定環(huán)境。run(String... args):啟動應用。
示例:
new SpringApplicationBuilder()
.sources(MyApplication.class)
.initializers(new MyContextInitializer())
.listeners(new MyReadyListener())
.profiles("dev")
.run(args);
6.3.3 自定義ApplicationContextInitializer示例
public class MyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContext初始化邏輯...");
// 可以動態(tài)注冊Bean、修改環(huán)境變量等
}
}6.4 小結
- Spring Boot啟動流程通過
SpringApplicationRunListener提供了豐富的啟動階段監(jiān)聽能力,適合執(zhí)行啟動監(jiān)控、日志收集、資源準備等任務。 ApplicationListener基于Spring事件機制,支持對啟動事件及其他業(yè)務事件的監(jiān)聽,更加通用。SpringApplicationBuilder是鏈式構建啟動參數(shù)的工具,極大提升了啟動配置的靈活性和可維護性。
通過掌握這幾個核心擴展點,開發(fā)者可以對Spring Boot啟動流程進行高度自定義,滿足各種復雜的啟動需求,提升應用的健壯性和擴展能力。
7. Spring Boot啟動性能優(yōu)化
隨著企業(yè)級應用復雜度的提升,Spring Boot應用的啟動時間成為一個重要考量指標??焖賳硬粌H提升開發(fā)效率,還能改善云環(huán)境中的彈性擴縮容體驗。此章將圍繞啟動性能展開,講解啟動耗時分析方法、常見瓶頸,以及針對性的優(yōu)化策略。
7.1 啟動耗時分析:利用StopWatch
7.1.1 StopWatch在Spring Boot中的應用
在SpringApplication.run()方法中,Spring Boot通過org.springframework.util.StopWatch來度量啟動各個階段的耗時,幫助開發(fā)者定位瓶頸。
簡化代碼片段(摘自SpringApplication.run()):
StopWatch stopWatch = new StopWatch();
stopWatch.start("SpringApplication啟動");
// 啟動流程中的重要階段代碼...
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());StopWatch會輸出類似如下的階段耗時報告:
StopWatch '': running time = 12345 ns
---------------------------------------------
ns % Task name
---------------------------------------------
5000000 40% SpringApplication啟動
7500000 60% ApplicationContext刷新
7.1.2 使用啟動日志和--debug參數(shù)定位耗時
Spring Boot支持通過命令行參數(shù)--debug開啟詳細日志,打印啟動過程中的細節(jié)信息,便于發(fā)現(xiàn)耗時較長的組件或階段。
java -jar myapp.jar --debug
日志中會包含大量有用信息,如自動配置條件匹配、Bean創(chuàng)建時間等。
7.2 主要啟動性能瓶頸分析
- 自動配置掃描與條件判斷:復雜的
@Conditional注解邏輯,條件不匹配時仍需執(zhí)行判斷,影響啟動速度。 - Bean實例化和依賴注入:大量單例Bean的創(chuàng)建、復雜依賴關系的解析耗時較長。
- 環(huán)境變量和配置加載:讀取多層配置文件和系統(tǒng)屬性,尤其是網(wǎng)絡配置(如遠程配置中心)可能阻塞啟動。
- 類路徑掃描與組件掃描:掃描大量包及類時會增加啟動負擔。
- 嵌入式容器啟動:內嵌Tomcat、Jetty或Undertow啟動耗時不可忽視。
7.3 啟動性能優(yōu)化策略
7.3.1 使用@Lazy延遲加載Bean
通過給不必要立即加載的Bean添加@Lazy注解,Spring容器會延遲初始化,減少啟動時的Bean加載壓力。
示例:
@Configuration
public class MyConfig {
@Bean
@Lazy
public HeavyService heavyService() {
return new HeavyService();
}
}7.3.2 精準配置@ComponentScan掃描范圍
縮小@ComponentScan的掃描范圍,避免掃描無關包,減少類路徑掃描時間。
@SpringBootApplication(scanBasePackages = "com.example.myapp.service")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
7.3.3 禁用不必要的自動配置
通過@SpringBootApplication(exclude = {AutoConfigurationClass.class})排除不需要的自動配置類,避免無用配置初始化。
示例:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { ... }
7.3.4 自定義自動配置條件減少啟動判斷
簡化自動配置中的@Conditional注解判斷邏輯,避免復雜條件鏈。
7.3.5 采用Spring Boot Layered JAR優(yōu)化啟動
Spring Boot 2.3+支持分層Jar結構,將應用、依賴、啟動器分開打包,有助于加速Docker鏡像啟動。
7.4 實踐示例:啟動時間統(tǒng)計與延遲加載
@SpringBootApplication
public class PerformanceApp {
public static void main(String[] args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("啟動應用");
SpringApplication app = new SpringApplication(PerformanceApp.class);
app.run(args);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
@Bean
@Lazy
public ExpensiveBean expensiveBean() {
System.out.println("初始化ExpensiveBean...");
return new ExpensiveBean();
}
}運行該程序,可以看到啟動階段時間統(tǒng)計,并且expensiveBean只有在首次調用時才初始化。
7.5 總結
- 通過
StopWatch和--debug日志,能有效定位啟動耗時階段。 - 延遲加載、精準掃描、排除無用自動配置是主要優(yōu)化手段。
- 優(yōu)化啟動性能有助于提升開發(fā)效率、加快部署速度、降低云環(huán)境資源消耗。
到此這篇關于Spring Boot啟動流程詳解的文章就介紹到這了,更多相關Spring Boot啟動流程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot整合Spring?Boot?Admin實現(xiàn)服務監(jiān)控的方法
這篇文章主要介紹了SpringBoot整合Spring?Boot?Admin實現(xiàn)服務監(jiān)控,內容包括Server端服務開發(fā),Client端服務開發(fā)其中Spring Boot Admin還可以對其監(jiān)控的服務提供告警功能,如服務宕機時,可以及時以郵件方式通知運維人員,感興趣的朋友跟隨小編一起看看吧2022-03-03
SpringCloud Zuul和Gateway的實例代碼(搭建方式)
本文主要介紹了SpringCloudZuul和SpringCloudGateway的簡單示例,SpringCloudGateway是推薦使用的API網(wǎng)關解決方案,基于SpringFramework5和ProjectReactor構建,具有更高的性能和吞吐量2025-02-02
Spring BeanPostProcessor源碼示例解析
這篇文章主要為大家介紹了Spring BeanPostProcessor源碼示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法
這篇文章主要介紹了java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法,在具體生成id的時候,我們的操作順序一般是:先在主表中插入記錄,然后獲得自動生成的id,以它為基礎插入從表的記錄,需要的朋友可以參考下2019-06-06
Maven在Java8下如何忽略Javadoc的編譯錯誤詳解
這篇文章主要給大家介紹了關于Maven在Java8下如何忽略Javadoc的編譯錯誤的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-08-08
idea使用war以及war exploded的區(qū)別說明
本文詳細解析了war與warexploded兩種部署方式的差異及步驟,war方式是先打包成war包,再部署到服務器上;warexploded方式是直接把文件夾、class文件等移到Tomcat上部署,支持熱部署,開發(fā)時常用,文章分別列出了warexploded模式和war包形式的具體操作步驟2024-10-10

