Spring Boot啟動(dòng)加載流程自動(dòng)配置的底層原理(專家解讀)
在使用 Spring Boot 時(shí),開發(fā)者常常會(huì)感到驚訝:為什么這么少的配置卻能實(shí)現(xiàn)如此豐富的功能?為何啟動(dòng)一個(gè) Spring Boot 應(yīng)用可以變得如此簡單便捷?在這個(gè)看似平凡的啟動(dòng)過程中,其實(shí)隱藏著一套精妙的自動(dòng)化機(jī)制和源碼邏輯。本文將帶你深入探索 Spring Boot 的啟動(dòng)流程,逐步揭開自動(dòng)配置的幕后奧秘。通過了解這些底層原理,你將發(fā)現(xiàn),原來每一次應(yīng)用的無縫啟動(dòng)都是精心設(shè)計(jì)的結(jié)果,讓你對(duì) Spring Boot 有更深刻的理解和掌控力!
一、整體概述
(一)基本整體初步分析
Spring Boot 是一個(gè)用于構(gòu)建獨(dú)立的、生產(chǎn)級(jí)的 Spring 應(yīng)用程序的框架,它提供了自動(dòng)化的配置和約定優(yōu)于配置的原則。在理解 Spring Boot 的啟動(dòng)配置原理之前,我們需要了解幾個(gè)關(guān)鍵概念。
首先,Spring Boot 使用了基于約定的自動(dòng)配置機(jī)制。它通過在 classpath 下查找特定的配置文件和類,根據(jù)應(yīng)用程序所使用的依賴自動(dòng)配置 Spring 應(yīng)用程序的各種組件。這樣可以大大簡化開發(fā)者的工作,減少了手動(dòng)配置的需求。
其次,Spring Boot 使用了條件化配置(Conditional Configuration)的機(jī)制。這意味著配置的應(yīng)用取決于一組條件是否滿足。條件可以基于多種因素,如 classpath 中存在特定的類、特定的 bean 是否存在等等。通過條件化配置,Spring Boot 可以根據(jù)不同的環(huán)境和需求進(jìn)行動(dòng)態(tài)的配置。
Spring Boot 的啟動(dòng)配置原理可以概括如下:
- 在啟動(dòng)過程中,Spring Boot 會(huì)加載并解析應(yīng)用程序的配置文件,其中包括 application.properties 或 application.yml 文件等。這些文件中可以定義各種屬性和配置信息,如數(shù)據(jù)庫連接、日志級(jí)別等。
- Spring Boot 會(huì)自動(dòng)掃描 classpath 下的特定包,尋找?guī)в刑囟ㄗ⒔獾念?,?@SpringBootApplication。這個(gè)注解標(biāo)識(shí)了一個(gè) Spring Boot 應(yīng)用程序的入口點(diǎn)。
- 根據(jù)配置文件中的屬性和條件化配置的機(jī)制,Spring Boot 自動(dòng)配置應(yīng)用程序的各種組件,包括數(shù)據(jù)庫連接池、消息隊(duì)列、Web 服務(wù)器等。如果需要進(jìn)行自定義配置,可以使用專門的注解或編寫自定義的配置類。
- 在應(yīng)用程序啟動(dòng)時(shí),Spring Boot 會(huì)初始化 Spring 容器,并根據(jù)配置進(jìn)行相應(yīng)的初始化工作。這包括創(chuàng)建和管理 bean、處理依賴注入等。
總的來說,Spring Boot 的啟動(dòng)配置原理是基于自動(dòng)化的約定和條件化配置機(jī)制。它通過讀取配置文件、掃描注解、自動(dòng)配置組件等步驟,簡化了應(yīng)用程序的配置過程,并提供了靈活性和易用性。
(二)從啟動(dòng)來看整體過程圖分析
每個(gè)SpringBoot程序都有一個(gè)主入口main方法,main里面調(diào)用SpringApplication.run()啟動(dòng)整個(gè)SpringBoot程序,該方法所在類需要使用@SpringBootApplication注解,例如如下
package org.zyf.javabasic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 描述:啟動(dòng)入口類
*
* @author yanfengzhang
* @date 2019-12-19 18:11
*/
@SpringBootApplication
@ComponentScan(basePackages = {"org.zyf.javabasic"})
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@EnableSwagger2
public class ZYFApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ZYFApplication.class, args);
}
其中對(duì)@SpringBootApplication進(jìn)行展開分析:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication包括三個(gè)注解,功能如下:
- @SpringBootConfiguration(內(nèi)部為@Configuration):被標(biāo)注的類等于在spring的XML配置文件中(applicationContext.xml),裝配所有bean事務(wù),提供了一個(gè)spring的上下文環(huán)境
- @ComponentScan:組件掃描,可自動(dòng)發(fā)現(xiàn)和裝配Bean(比如@Component和@Configuration),默認(rèn)掃描SpringApplication的run方法里類所在的包路徑下所有文件
- @EnableAutoConfiguration:激活SpringBoot自動(dòng)裝配的特性
現(xiàn)在對(duì)主入口main方法進(jìn)行展開來給出整體的流程圖分析

二、SpringApplication構(gòu)造過程分析
進(jìn)入main里面的run方法,創(chuàng)建了一個(gè)SpringApplication實(shí)例,配置一些基本的環(huán)境變量、資源、構(gòu)造器、監(jiān)聽器,進(jìn)入這個(gè)SpringApplication有參構(gòu)造函數(shù)
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}注意:在該構(gòu)造方法內(nèi),做的工作就是把相關(guān)的類(主要是initializer和listener)加載進(jìn)容器中,并沒有執(zhí)行
(一)驗(yàn)證主配置類不為空并且保存主類

(二)推斷項(xiàng)目的類型

進(jìn)入對(duì)應(yīng)方法分析如下:

推斷項(xiàng)目的類型可能為reactive、none、servlet三種類型,默認(rèn)為servlet類型。其使用類加載器判斷類型的邏輯如下
類型 | 判斷情況 |
|---|---|
reactive | 存在Spring WebFlux的DispatcherHandler存在,但是Spring MVC的DispatcherServlet不存在 |
none | 二者都不存在 |
servlet | 剩余所有情況 |
(三)初始化initializers

先進(jìn)入ApplicationContextInitializer接口,這個(gè)接口只有一個(gè)方法initialize
package org.springframework.context;
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}查看實(shí)現(xiàn)了該這個(gè)接口的類進(jìn)行分析

以SharedMetadataReaderFactoryContextInitializer實(shí)現(xiàn)類為例,跳轉(zhuǎn)對(duì)應(yīng)jar包,可以看到里面 spring.factories就在key為ApplicationContextInitializer中指定了對(duì)應(yīng)的實(shí)現(xiàn)類,例如:

進(jìn)入SharedMetadataReaderFactoryContextInitializer,其實(shí)現(xiàn)了ApplicationContextInitializer接口,并實(shí)現(xiàn)了里面的initialize方法

現(xiàn)在回到一開始再來看getSpringFactoriesInstance()方法,其核心為loadFactoryNames()方法

進(jìn)入loadFactoryNames()方法,可以看到ApplicationContextInitializer的相關(guān)獲取內(nèi)容直接就是從文件“META-INF/spring.factories”中獲取保存的

(四)加載相關(guān)的listeners

先進(jìn)入ApplicationListener接口,這個(gè)接口只有一個(gè)方法onApplicationEvent
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}查看實(shí)現(xiàn)了該這個(gè)接口的類進(jìn)行分析

以BackgroundPreinitializer實(shí)現(xiàn)類為例,跳轉(zhuǎn)對(duì)應(yīng)jar包,可以看到里面 spring.factories就在key為ApplicationListener中指定了對(duì)應(yīng)的實(shí)現(xiàn)類:

進(jìn)入BackgroundPreinitializer,其實(shí)現(xiàn)了ApplicationListener,并實(shí)現(xiàn)了里面的onApplicationEvent
方法如下:

現(xiàn)在回到一開始再來看getSpringFactoriesInstance()方法,其處理流程和上面的一樣,即依然是從類路徑下找到META-INF/spring.factories配置的所有ApplicationListener
(五)決定ApplicationClass主程序

進(jìn)入deduceMainApplicationClass方法

三、SpringApplication啟動(dòng)過程分析

SpringBoot啟動(dòng)方案,包括啟動(dòng)流程的監(jiān)聽模塊、加載配置環(huán)境模塊、及核心的創(chuàng)建上下文環(huán)境模塊以及后續(xù)的收尾回調(diào)等內(nèi)容
(一)監(jiān)控器監(jiān)聽容器啟動(dòng)并進(jìn)行圖形化頁面處理

(二)監(jiān)聽器SpringApplicationRunListeners開啟監(jiān)聽

直接開始分析ApringApplicationRunListeners,內(nèi)含SpringApplicationRunListener的集合,其中starting方法就是對(duì)listeners進(jìn)行遍歷,對(duì)每個(gè)listener都調(diào)用starting方法

SpringApplicationRunListener和ApplicationListener都是SpringBoot中的事件監(jiān)聽器,但是它們所監(jiān)聽的事件和觸發(fā)時(shí)機(jī)有所不同,其區(qū)別如下:
- 監(jiān)聽的事件不同
SpringApplicationRunListener主要監(jiān)聽SpringApplication運(yùn)行時(shí)的各種事件,例如應(yīng)用程序開始啟動(dòng)、應(yīng)用程序啟動(dòng)失敗、應(yīng)用程序啟動(dòng)完成等事件。而ApplicationListener主要監(jiān)聽Spring容器中的各種事件,例如Bean加載完成、上下文刷新完成等事件。 - 觸發(fā)時(shí)機(jī)不同
SpringApplicationRunListener在SpringApplication啟動(dòng)時(shí)就開始工作,可以接收到應(yīng)用程序開始啟動(dòng)、應(yīng)用程序啟動(dòng)失敗、應(yīng)用程序啟動(dòng)成功等各種事件。而ApplicationListener則是在Spring容器啟動(dòng)完成后,才能開始工作,監(jiān)聽的是Spring容器中的各種事件。 - 使用場景不同
在實(shí)際應(yīng)用中,SpringApplicationRunListener主要用于監(jiān)聽SpringApplication的啟動(dòng)過程,例如在應(yīng)用程序啟動(dòng)前后執(zhí)行某些操作、監(jiān)聽?wèi)?yīng)用程序啟動(dòng)失敗事件并做出相應(yīng)的操作等。而ApplicationListener則用于監(jiān)聽Spring容器中的各種事件,例如在Bean加載完成后做出相應(yīng)的操作、在上下文刷新完成后更新一些狀態(tài)等。
總之,盡管SpringApplicationRunListener和ApplicationListener都是SpringBoot中的事件監(jiān)聽器,但是它們所監(jiān)聽的事件、觸發(fā)時(shí)機(jī)、使用場景等都有所不同,我們需要根據(jù)具體的應(yīng)用需求,選擇合適的監(jiān)聽器來完成應(yīng)用程序的事件處理。
在進(jìn)入看一下SpringApplicationRunListener這個(gè)類
package org.springframework.boot;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context, Throwable exception);
}對(duì)應(yīng)方法說明如下
監(jiān)聽方法 | 運(yùn)行階段說明 | SpringBoot起始版本 |
|---|---|---|
contextLoaded(ConfigurationApplicationEnvironment) | ConfigurableApplicationContext完成加載,但仍未啟動(dòng);通知監(jiān)聽器,ApplicationContext已經(jīng)完成IoC配置 | 1.0 |
contextPrepared(ConfigurationApplicationEnvironment) | ConfigurableApplicationContext準(zhǔn)備妥當(dāng):通知監(jiān)聽器,ApplicationContext已經(jīng)創(chuàng)建并初始化完成 | 1.0 |
environmentPrepared(ConfigurationEnvironment) | ConfigurationEnvironment準(zhǔn)備妥當(dāng),允許將其調(diào)整 | 1.0 |
failed(ConfigurationApplicationEnvironment,Throwable) | Spring應(yīng)用運(yùn)行失敗 | 2.0 |
running(ConfigurationApplicationEnvironment) | Spring應(yīng)用正在運(yùn)行 | 2.0 |
started(ConfigurationApplicationEnvironment) | ConfigurableApplicationContext已啟動(dòng),此時(shí)SpringBean已初始化完成 | 2.0 |
starting() | run方法執(zhí)行的時(shí)候立馬執(zhí)行:通知監(jiān)聽器,SpringBoot開始執(zhí)行 | 1.0 |
總的來說就是創(chuàng)建了應(yīng)用的監(jiān)聽器SpringApplicationRunListeners并調(diào)用start()開始監(jiān)聽,先在getRunListeners中獲取了所有的監(jiān)聽器,然后starting開啟
(三)environmentPrepared環(huán)境準(zhǔn)備處理

分析這段代碼,進(jìn)入準(zhǔn)備環(huán)境的方法prepareEnvironment中,可以看到如下:

首先創(chuàng)建了一個(gè)環(huán)境ConfigurationEnvironment(有的話就獲取,沒有則創(chuàng)建)

回到該方法返回的地方,通過this.configureEnvironment對(duì)環(huán)境進(jìn)行設(shè)置,接著如下:

環(huán)境配置好以后回調(diào)了SpringApplicationRunListener的environmentPrepared函數(shù),進(jìn)入該方法:

可以看到environmentPrepared環(huán)境準(zhǔn)備中進(jìn)行了通知監(jiān)聽器,Environment準(zhǔn)備完成。
回到開始處,可以看到環(huán)境準(zhǔn)備完成后通過bindToSpringApplication將環(huán)境綁定到程序中。
(四)banner打印

打印對(duì)應(yīng)的banner信息

也就是啟動(dòng)后的改部分,見如下

實(shí)際中,改圖可以進(jìn)行替換,只需要在resources增加banner.txt信息即可,例如如下:

其中的原理可以繼續(xù)點(diǎn)開對(duì)應(yīng)后續(xù)代碼分析,這個(gè)后續(xù)中講解替換思路。
(五)創(chuàng)建Spring應(yīng)用上下文

創(chuàng)建應(yīng)用上下文即IOC過程,IOC容器是驅(qū)動(dòng)整體SpringBoot應(yīng)用組件的核心,進(jìn)入該方法:

這里的創(chuàng)建是根據(jù)SpringApplication在構(gòu)造階段所推斷的web應(yīng)用類型進(jìn)行IOC容器的創(chuàng)建,IOC容器就是run返回的內(nèi)容。
(六)Spring應(yīng)用上下文準(zhǔn)備階段

prepareContext方法將listeners、environment、applicationArguments、banner等重要組件與上下文對(duì)象關(guān)聯(lián),對(duì)上下文對(duì)象進(jìn)行進(jìn)一步配置,進(jìn)入該方法具體分析:

可以看到首先分別保存了剛才生成的environment、ApplicationContext,接下來的applyInitializers方法是執(zhí)行初始化,進(jìn)入該方法

方法內(nèi)部遍歷所有的initializer,然后依次回調(diào)里面所有的initialize方法(這些initializer就是在springboot剛啟動(dòng)時(shí)構(gòu)造new springApplication時(shí)添加的),設(shè)置完當(dāng)前環(huán)境并完成初始化之后,回調(diào)了所有Linsteners的contextPrepared方法

接下來將命令行參數(shù)和banner注冊(cè)到IOC容器來,如下:

最后全部操作都完成后,這個(gè)方法回調(diào)了Listeners的contextLoaded方法,如上。
(七)Spring應(yīng)用上下文刷新階段

在該方法中首先注冊(cè)了一個(gè)shutdownHook線程,用來實(shí)現(xiàn)SpringBean銷毀生命周期回調(diào)

在執(zhí)行完refresh之后的控制臺(tái),可以看到tomcat和一些IOC容器的bean都被加載進(jìn)去了

(八)Spring應(yīng)用上下文收尾階段

其中afrerRefresh()并無內(nèi)容處理,后續(xù)的版本中已經(jīng)沒有改方法了
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}收尾計(jì)時(shí)器停止,同時(shí)調(diào)用監(jiān)聽器的started()。
(九)回調(diào)工作處理

進(jìn)入該方法分析

ApplicationContext就是IOC容器,這個(gè)方法從IOC容器中獲取了所有的ApplicationRunner和ConmmandLineRunner,接下來進(jìn)行遍歷和回調(diào)。
在callRunners使用的這兩個(gè)類幾乎可以等價(jià),都是用于做一些客戶自定義的工作,而且是整個(gè)流程完成之后才會(huì)調(diào)用用戶自己定義的實(shí)現(xiàn)類的run方法,這兩個(gè)run方法的實(shí)現(xiàn)方法都是在容器基本初始化好的時(shí)候調(diào)用的。
緊接著,如果無異常代碼執(zhí)行如下:

監(jiān)聽器回調(diào)running()方法代表SpringApplication正常啟動(dòng)結(jié)束。
(十)SpringApplication啟動(dòng)異常處理

發(fā)生異常主要是對(duì)異常的處理,我們進(jìn)入該方法分析

可以看到這個(gè)異常報(bào)告類也是支持自定義并且自動(dòng)配置的,配置結(jié)束后,Springboot做了一些基本的收尾工作,返回了應(yīng)用環(huán)境上下文(IOC容器)。
四、SpringBoot自動(dòng)配置分析
Spring Boot 的自動(dòng)化配置模塊是該框架的核心功能之一,它可以大大簡化應(yīng)用程序的配置工作。下面是對(duì) Spring Boot 自動(dòng)化配置模塊的講解和分析:
- 自動(dòng)化配置的原理:Spring Boot 的自動(dòng)化配置模塊基于約定優(yōu)于配置的原則。它通過在 classpath 下掃描依賴和配置,自動(dòng)配置應(yīng)用程序的各個(gè)組件。它使用條件化配置的機(jī)制,根據(jù)環(huán)境和條件自動(dòng)選擇適當(dāng)?shù)呐渲谩?/strong>
- 自動(dòng)配置的實(shí)現(xiàn)方式:Spring Boot 自動(dòng)化配置模塊使用了 @Conditional 注解和條件注解來實(shí)現(xiàn)條件化配置。這些注解可以根據(jù)一組條件來決定是否啟用某個(gè)配置。例如,@ConditionalOnClass 根據(jù) classpath 中是否存在指定的類來判斷是否啟用配置。
- 自動(dòng)配置的加載順序:Spring Boot 的自動(dòng)配置是通過在 classpath 下的 META-INF/spring.factories 文件中定義的自動(dòng)配置類來實(shí)現(xiàn)的。這些自動(dòng)配置類會(huì)被自動(dòng)加載,并根據(jù)條件進(jìn)行初始化和配置。根據(jù)條件的不同,可以有多個(gè)自動(dòng)配置類被加載,它們會(huì)按照優(yōu)先級(jí)順序進(jìn)行配置。
- 自動(dòng)配置的自定義:Spring Boot 允許開發(fā)者對(duì)自動(dòng)配置進(jìn)行自定義。你可以使用 @Conditional 注解和條件注解來定義自定義的條件,從而影響自動(dòng)配置的行為。你還可以使用 @EnableAutoConfiguration 注解來控制自動(dòng)配置的啟用或禁用。
- 自動(dòng)配置的好處:Spring Boot 的自動(dòng)化配置模塊帶來了很多好處。它大大減少了手動(dòng)配置的工作量,提高了開發(fā)效率。它提供了合理的默認(rèn)配置,減少了錯(cuò)誤配置的風(fēng)險(xiǎn)。同時(shí),它的條件化配置機(jī)制使得應(yīng)用程序更具靈活性,能夠根據(jù)不同的環(huán)境和需求進(jìn)行動(dòng)態(tài)的配置。
總的來說,Spring Boot 的自動(dòng)化配置模塊是該框架的重要特性之一。它通過約定優(yōu)于配置的原則和條件化配置機(jī)制,實(shí)現(xiàn)了自動(dòng)加載和配置應(yīng)用程序的各個(gè)組件。這為開發(fā)者提供了便利和靈活性,并大大簡化了應(yīng)用程序的配置過程。
現(xiàn)在回到我們一開始的圖示分析,該配置模塊的主要使用到了SpringFactoriesLoader,即Spring工廠加載器,該對(duì)象提供了loadFactoryNames方法,入?yún)閒actoryClass和classLoader,即需要傳入工廠類名稱和對(duì)應(yīng)的類加載器,方法會(huì)根據(jù)指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件,傳入的工廠類為接口,獲取到這些實(shí)現(xiàn)類的類名后,loadFactoryNames方法返回類名集合,方法調(diào)用方得到這些集合后,再通過反射獲取這些類的類對(duì)象、構(gòu)造方法,最終生成實(shí)例。
(一)自動(dòng)裝配原理分析
從@SpringBootApplication中的@EnableAutoConfiguration注解中可以看到其import了一個(gè)自動(dòng)配置導(dǎo)入選擇器AutoConfigurationImportSelect
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}其類圖如下

可以發(fā)現(xiàn)其最終實(shí)現(xiàn)了ImportSelector(選擇器)和BeanClassLoaderAware(bean類加載器中間件),其中這個(gè)選擇器的作用就是導(dǎo)入組件。
所有自動(dòng)裝配的邏輯都是在AutoConfigurationImportSelector里面的selectImports方法中實(shí)現(xiàn)的

進(jìn)入getAutoConfigurationEntry()方法

進(jìn)入getCandidateConfigurations()獲取候選配置方法,可以看到核心的SpringFactoriesLoader的loadFactoryNames()方法

其中SpringFactoriesLoader是Spring Framework工廠機(jī)制的加載器,loadFactoryNames是其對(duì)應(yīng)的加載方法,進(jìn)入這個(gè)核心的loadFactoryNames方法中查看

該處加載原理如下:
- 掃描所有jar包路徑下 META-INF/spring.factories,這里是通過類加載器生成對(duì)應(yīng)的url路徑
- 把掃描到的內(nèi)容包裝成properties對(duì)象,并對(duì)這個(gè)對(duì)象的內(nèi)容進(jìn)行遍歷,返回map,map的key為接口的全類名,value為接口全部實(shí)現(xiàn)類列表(列表里元素去重,防止重復(fù)加載),這個(gè)value的信息后續(xù)會(huì)作為loadSpringFactories方法的返回值
- 在上一步返回的map之中查找并返回指定類名映射的實(shí)現(xiàn)類全類名列表
再看剛才getCandidateConfiguration方法中的getSpringFactoriesLoaderFactoryClass方法,返回的就是EnableAutoConfiguration類

也就是說要從剛才properties中再獲取這個(gè)類對(duì)應(yīng)的值,把它們加到容器中。
選取mybatis-spring-boot-autoconfigure下的spring.factories文件分析一下:

每一個(gè) xxxAutoConfiguration類都是容器中的一個(gè)組件,都加入到容器中,用它們做自動(dòng)配置;
只有進(jìn)入到容器中,這些自動(dòng)配置類才會(huì)起作用——進(jìn)行自動(dòng)配置功能
(二)條件化自動(dòng)裝配
對(duì)于使用@Configuration的自動(dòng)配置類,其條件自動(dòng)化裝配以@condition為核心。在spring底層中的@conditional注解會(huì)根據(jù)不同的滿足條件生效整個(gè)配置類里面的配置。
條件化裝配可以分為以下幾類:
Class條件注解
注解 | 說明 |
|---|---|
@ConditionalOnClass | 指定類存在時(shí)生效 |
@ConditionalOnMissingClass | 指定類缺失時(shí)生效 |
Bean條件注解
注解 | 說明 |
|---|---|
@ConditionalOnBean | 指定Bean存在時(shí)生效 |
@ConditionalOnMissingBean | 指定Bean缺失時(shí)生效 |
屬性條件注解
注解 | 說明 |
|---|---|
@ConditionalOnProperty | 使用屬性(application.properties)的值判斷是否生效 |
Web應(yīng)用條件注解
注解 | 說明 |
|---|---|
@ConditionalOnWebApplication | 是web類型時(shí)生效 |
@ConditionalOnNotWebApplication | 不是web類型時(shí)生效 |
其他條件注解
@Conditional擴(kuò)展注解 | 作用(判斷是否滿足當(dāng)前指定條件) |
|---|---|
@ConditionalOnJava | 系統(tǒng)的java版本是否符合要求 |
@ConditionalOnExpression | 滿足SpEL表達(dá)式指定 |
@ConditionalOnSingleCandidate | 容器中只有一個(gè)指定的Bean,或者這個(gè)Bean是首選Bean |
@ConditionalOnResource | 類路徑下是否存在指定資源文件 |
@ConditionalOnJndi | JNDI存在指定項(xiàng) |
分析@ConditionalOnWebApplication
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;
public static enum Type {
ANY,
SERVLET,
REACTIVE;
private Type() {
}
}
}進(jìn)入OnWebApplicationCondition類,里面有一個(gè)getMatchOutcome()方法,就是判斷是否符合當(dāng)前配置條件

該方法首先判斷是否使用了這個(gè)注釋,然后使用isWebApplication來判斷當(dāng)前是否是web應(yīng)用,這些match的方法判斷指定的條件成立,才會(huì)給容器添加組件,配置內(nèi)容才會(huì)生效。
(三)自動(dòng)配置原理舉例:HttpEncodingAutoConfiguration(HTTP編碼自動(dòng)配置)
可以看到以下信息:
- @Configuration 代表這是一個(gè)配置類,類似編寫的配置文件,也可以給容器中添加組件。
- @EnableConfigurationProperties 啟用指定類的ConfigurationProperties功能,將配置文件application.properties的值和ServerProperties綁定起來,并把ServerProperties加入到IOC容器中。
- @ConditionalOnClass用來判斷當(dāng)前項(xiàng)目是否含有這個(gè)類,這里CharacterEncodingFilter的作用就是springMVC亂碼解決的過濾器(以前在spring的xml文件中配置的),如果有這個(gè)過濾器則配置生效。
- @ConditionalOnProperty判斷這個(gè)配置是否存在,matchIfMissing = true代表即使配置文件中不存在這個(gè)屬性也是默認(rèn)生效的。
需要注意的是,在spring.factories中的自動(dòng)配置類不是都能生效的,都有各自的生效條件。根據(jù)當(dāng)前不同條件判斷,來決定這個(gè)配置類是否生效;一旦配置類生效,這個(gè)配置類就會(huì)給容器添加各種組件,這些組件的屬性從對(duì)應(yīng)的properties中獲取,這些類里面的每一個(gè)屬性又是和配置文件綁定的。
五、總結(jié)
Spring Boot 的自動(dòng)化配置和啟動(dòng)流程為開發(fā)者提供了簡化的開發(fā)體驗(yàn),降低了配置和啟動(dòng)的復(fù)雜性。通過深入理解 SpringApplication 的啟動(dòng)步驟、自動(dòng)配置的原理以及 Spring Boot 如何無縫集成各類組件,我們不僅能更好地運(yùn)用 Spring Boot,還能更靈活地定制和優(yōu)化應(yīng)用。自動(dòng)配置是 Spring Boot 的核心之一,它通過精巧的條件判斷實(shí)現(xiàn)了“按需配置”,確保應(yīng)用在減少冗余的前提下獲得所需的功能支持。
在實(shí)際開發(fā)中,理解這些原理可以幫助我們?cè)谟龅絾栴}時(shí)更快找到原因,尤其是當(dāng)我們需要進(jìn)行調(diào)優(yōu)或者定制配置時(shí),更能從容應(yīng)對(duì)。希望通過本文的分析,你能夠更全面地掌握 Spring Boot 的核心機(jī)制,并在實(shí)際項(xiàng)目中充分發(fā)揮它的優(yōu)勢。
到此這篇關(guān)于Spring Boot啟動(dòng)加載流程自動(dòng)配置的底層原理的文章就介紹到這了,更多相關(guān)Spring Boot啟動(dòng)過程與自動(dòng)配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解
這篇文章主要介紹了Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解,spring?boot拋棄了傳統(tǒng)xml配置文件,通過配置類(標(biāo)注@Configuration的類,@Configuration配置類相當(dāng)于一個(gè)xml配置文件)以JavaBean形式進(jìn)行相關(guān)配置,需要的朋友可以參考下2023-09-09
SpringMVC中@RequestMapping注解用法實(shí)例
通過@RequestMapping注解可以定義不同的處理器映射規(guī)則,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中@RequestMapping注解用法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Java WebSocket客戶端接收大量數(shù)據(jù)的三種方案
WebSocket是一種基于TCP協(xié)議的全雙工通信協(xié)議,它能夠在客戶端和服務(wù)器之間建立一個(gè)持久連接,實(shí)現(xiàn)實(shí)時(shí)的雙向數(shù)據(jù)傳輸,在實(shí)際應(yīng)用中,有時(shí)候我們需要處理大量的數(shù)據(jù),所以本文將介紹如何使用 Java WebSocket 客戶端接收大量數(shù)據(jù),并提供一些優(yōu)化方案2023-11-11
SpringBoot @CompentScan excludeFilters配置無效的解決方案
這篇文章主要介紹了SpringBoot @CompentScan excludeFilters配置無效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Mybatis Plus 大數(shù)據(jù)游標(biāo)分頁的實(shí)現(xiàn)
使用MyBatis Plus的游標(biāo)分頁,我們可以輕松應(yīng)對(duì)大數(shù)據(jù)量的場景,本文主要介紹了Mybatis Plus 大數(shù)據(jù)游標(biāo)分頁的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07
Java 獲取服務(wù)器環(huán)境的實(shí)例詳解
這篇文章主要介紹了Java 獲取服務(wù)器環(huán)境的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例和輸出結(jié)果,希望能幫助大家理解,需要的朋友可以參考下2017-07-07

