SpringBoot啟動原理詳解(圖文全面總結)

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

雖然我們在日常開發中,Spring Boot 使用非常多,算是目前 Java 開發領域一個標配了,但是小夥伴們仔細想想自己的面試經歷,和 Spring Boot 相關的面試題都有哪些?個人感覺應該是比較少的,Spring Boot 本質上還是曾經 SSM 那一套,只是透過各種 starter 簡化了配置而已,其他都是一模一樣的,所以 Spring Boot 中很多面試題還是得迴歸到 Spring 中去解答!當然這並不是說 Spring Boot 中沒什麼可問的,Spring Boot 中其實也有一個非常經典的面試題,那就是 Spring Boot 的啟動原理是什麼?今天就來和各位小夥伴聊一下這個問題。
其實之前和小夥伴們聊過相關的問題,不過都是零散的,沒有系統梳理過,之前也帶領小夥伴們自定義過一個 starter,相信各位小夥伴對於 starter 的原理也有一定了解,所以今天這篇文章一些過於細節的內容我就不贅述了。

一 @SpringBootApplication

要說 Spring Boot 的自動化配置,那必須從專案的啟動類 @SpringBootApplication 說起,這是整個 Spring Boot 宇宙的起點,我們先來看下這個註解:
@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

 @

interfaceSpringBootApplication

{
}

可以看到,@SpringBootApplication 註解組合了多個常見註解的功能,其中:
  • 前四個是元註解,這裡我們不做討論。
  • 第五個 @SpringBootConfiguration 是一個支援配置類的註解,這裡我們也不做討論。
  • 第六個 @EnableAutoConfiguration 這個註解就表示開啟自動化配置,這是我們今天要聊得重點。
  • 第七個 @ComponentScan 是一個包掃描註解,為什麼 Spring Boot 專案中的 Bean 只要放對位置就會被自動掃描到,和這個註解有關。
別看這裡註解多,其實真正由 Spring Boot 提供的註解一共就兩個,分別是 @SpringBootConfiguration@EnableAutoConfiguration 兩個,其他註解在 Spring Boot 出現之前就已經存在多年了。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

二 @EnableAutoConfiguration

接下來我們來看看 @EnableAutoConfiguration 是如何實現自動化配置的。
@Target

(ElementType.TYPE)

@Retention

(RetentionPolicy.RUNTIME)

@Documented
@Inherited
@AutoConfigurationPackage
@Import

(AutoConfigurationImportSelector

.

class

)

public

 @

interfaceEnableAutoConfiguration

{
}

這個註解起關鍵作用的就是兩個東西:
  1. @AutoConfigurationPackage:這個表示自動掃描各種第三方的註解
  2. @Import 則是在匯入 AutoConfigurationImportSelector 配置類,這個配置類裡邊就是去載入各種自動化配置類的。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

三 AutoConfigurationImportSelector

AutoConfigurationImportSelector 類中的方法比較多,入口的地方則是 process 方法,所以我們這裡就從 process 方法開始看起:
@Override
publicvoidprocess(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)

{

 Assert.state(deferredImportSelector 

instanceof

 AutoConfigurationImportSelector,

   () -> String.format(

"Only %s implementations are supported, got %s"

,

     AutoConfigurationImportSelector

.

class

.

getSimpleName

(),

deferredImportSelector

.

getClass

().

getName

()))

;

 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)

  .getAutoConfigurationEntry(annotationMetadata);

this

.autoConfigurationEntries.add(autoConfigurationEntry);

for

 (String importClassName : autoConfigurationEntry.getConfigurations()) {

this

.entries.putIfAbsent(importClassName, annotationMetadata);

 }

}

從類名就可以看出來,跟自動化配置相關的物件是由 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); 進行載入的。
當然這裡的 getAutoConfigurationEntry 方法實際上就是當前類提供的方法,我們來看下該方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)

{

if

 (!isEnabled(annotationMetadata)) {

return

 EMPTY_ENTRY;

 }

 AnnotationAttributes attributes = getAttributes(annotationMetadata);

 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

 configurations = removeDuplicates(configurations);

 Set<String> exclusions = getExclusions(annotationMetadata, attributes);

 checkExcludedClasses(configurations, exclusions);

 configurations.removeAll(exclusions);

 configurations = getConfigurationClassFilter().filter(configurations);

 fireAutoConfigurationImportEvents(configurations, exclusions);

returnnew

 AutoConfigurationEntry(configurations, exclusions);

}

這裡原始碼的方法命名都做的不錯,基本上都能做到見名知意,小夥伴們日常開發中,應該向這樣的命名思路看齊。接下來我們就來挨個看一下這裡的關鍵方法。

3.1 isEnabled

首先呼叫 isEnabled 方法去判斷自動化配置到底有沒有開啟,這個主要是因為我們及時在專案中引入了 spring-boot-starter-xxx 之後,我們也可以透過在 application.properties 中配置 spring.boot.enableautoconfiguration=false 來關閉所有的自動化配置。
相關原始碼如下:
protectedbooleanisEnabled(AnnotationMetadata metadata)

{

if

 (getClass() == AutoConfigurationImportSelector

.class

{

return

 getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean

.classtrue)

;

 }

returntrue

;

}

3.2 getCandidateConfigurations

接下來呼叫 getCandidateConfigurations 方法去獲取所有候選的自動化配置類,這些候選的自動化配置類主要來自兩個地方:
  1. 在之前的自定義 starter 中松哥和大家聊過,我們需要在 claspath\:META-INF/spring.factories 中定義出來所有的自動化配置類,這是來源一。
  2. Spring Boot 自帶的自動化配置類,這個在之前的 vhr 影片中也和小夥伴們多次講過,Spring Boot 自帶的自動化配置類位於 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 檔案中。
相關原始碼如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)

{

 List<String> configurations = 

new

 ArrayList<>(

   SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));

 ImportCandidates.load(AutoConfiguration

.classgetBeanClassLoader()).forEach(configurations::add)

;

 Assert.notEmpty(configurations,

"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "

     + 

"are using a custom packaging, make sure that file is correct."

);

return

 configurations;

}

這裡載入到的自動化配置類的全路徑被存入到 configurations 物件中,該物件有兩個獲取的地方:
  1. 呼叫 SpringFactoriesLoader.loadFactoryNames 方法獲取,這個方法細節我就不帶大家看了,比較簡單,本質上就是去載入 META-INF/spring.factories 檔案,這個檔案中定義了大量的自動化配置類的全路徑。
  2. 呼叫 ImportCandidates.load 方法去載入,這個就是載入 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 檔案中的自動化配置類。
如果這兩個地方都沒有載入到任何自動化配置類,那麼就會丟擲一個異常。

3.3 removeDuplicates

removeDuplicates 方法表示移除候選自動化配置類中重複的類,移除的思路也很有意思,就用一個 LinkedHashSet 中轉一下就行了,原始碼如下:
protectedfinal

 <T> 

List<T> removeDuplicates(List<T> list)

{

returnnew

 ArrayList<>(

new

 LinkedHashSet<>(list));

}

可以看到這些原始碼裡有時候一些解決思路也很有意思。

3.4 getExclusions

getExclusions 方法表示需要獲取到所有被排除的自動化配置類,這些被排除的自動化配置類可以從三個地方獲取:
  1. 當前註解的 exclude 屬性。
  2. 當前註解的 excludeName 屬性。
  3. application.properties 配置檔案中的 spring.autoconfigure.exclude 屬性。
來看一下相關原始碼:
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)

{

 Set<String> excluded = 

new

 LinkedHashSet<>();

 excluded.addAll(asList(attributes, 

"exclude"

));

 excluded.addAll(asList(attributes, 

"excludeName"

));

 excluded.addAll(getExcludeAutoConfigurationsProperty());

return

 excluded;

}

跟上面講解的三點剛好對應。

3.5 checkExcludedClasses

這個方法是檢查所有被排除的自動化配置類,由於 Spring Boot 中的自動化配置類可以自定義,並不需要統一實現某一個介面或者統一繼承某一個類,所以在寫排除類的時候,如果寫錯了編譯是校驗不出來的,像下面這種:
@SpringBootApplication

(exclude = HelloController

.

class

)

publicclassApp

{

publicstaticvoidmain(String[] args)

{

        SpringApplication.run(App

.classargs)

;

    }

}

由於 HelloController 並不是一個自動化配置類,所以這樣寫專案啟動的時候就會報錯,如下:
這個異常從哪來的呢?其實就是來自 checkExcludedClasses 方法,我們來看下該方法:
privatevoidcheckExcludedClasses(List<String> configurations, Set<String> exclusions)

{

 List<String> invalidExcludes = 

new

 ArrayList<>(exclusions.size());

for

 (String exclusion : exclusions) {

if

 (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {

   invalidExcludes.add(exclusion);

  }

 }

if

 (!invalidExcludes.isEmpty()) {

  handleInvalidExcludes(invalidExcludes);

 }

}

protectedvoidhandleInvalidExcludes(List<String> invalidExcludes)

{

 StringBuilder message = 

new

 StringBuilder();

for

 (String exclude : invalidExcludes) {

  message.append(

"\t- "

).append(exclude).append(String.format(

"%n"

));

 }

thrownew

 IllegalStateException(String.format(

"The following classes could not be excluded because they are not auto-configuration classes:%n%s"

,

   message));

}

可以看到,在 checkExcludedClasses 方法中,會首先找到所有位於當前類路徑下但是卻不包含在 configurations 中的所有被排除的自動化配置類,由於 configurations 中的就是所有的自動化配置類了,所以這些不存在於 configurations 中的類都是有問題的,都不是自動化配置類,將這些有問題的類收集起來,存入到 invalidExcludes 變數中,然後再進行額外的處理。
所謂額外的處理就是在 handleInvalidExcludes 方法中丟擲異常,前面截圖中的異常就是來自這裡。

3.6 removeAll

這個方法就一個任務,就是從 configurations 中移除掉那些被排除的自動化配置類。configurations 本身就是 List 集合,exclusions 則是一個 Set 集合,所以這裡直接移除即可。

3.7 filter

現在我們已經載入了所有的自動化配置類了,但是這些配置類並不是都會生效,具體是否生效,還要看你的專案是否使用了具體的依賴。
例如,現在載入的自動化配置裡裡邊就包含了 RedisAutoConfiguration,這個是自動配置 Redis 的,但是由於我的專案中並沒有使用 Redis,所以這個自動化配置類並不會生效。這個過程就是由 getConfigurationClassFilter().filter(configurations); 來完成的。
先說一個預備知識:
由於我們專案中的自動化配置類特別多,每一個自動化配置類都會依賴別的類,當別的類存在時,這個自動化配置類才會生效,這一堆互相之間的依賴關係,存在於 spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties 檔案之中,我隨便舉一個該檔案中的配置:
  • org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit 表示 RabbitAnnotationDrivenConfiguration 類要生效有一個必備條件就是當前專案類路徑下要存在 org.springframework.amqp.rabbit.annotation.EnableRabbit
我們來看看 RabbitAnnotationDrivenConfiguration 類的註解:
@Configuration

(proxyBeanMethods = 

false

)

@ConditionalOnClass

(EnableRabbit

.

class

)

classRabbitAnnotationDrivenConfiguration

{

}

這個類和配置檔案中的內容一致。
這個預備知識搞懂了,接下來的內容就好理解了。
先來看 getConfigurationClassFilter 方法,這個就是獲取所有的過濾器,如下:
private ConfigurationClassFilter getConfigurationClassFilter()

{

if

 (

this

.configurationClassFilter == 

null

) {

  List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();

for

 (AutoConfigurationImportFilter filter : filters) {

   invokeAwareMethods(filter);

  }

this

.configurationClassFilter = 

new

 ConfigurationClassFilter(

this

.beanClassLoader, filters);

 }

returnthis

.configurationClassFilter;

}

可以看到,這裡獲取到的過濾器都是 AutoConfigurationImportFilter 型別的,這個型別的過濾器只有三個例項,如下圖:
從這三個例項的名字中,基本上就能看出來各自的作用:
  • OnClassCondition:這個就是條件註解 @ConditionalOnClass 的判定條件,看名字就知道用來判斷當前 classpath 下是否存在某個類。
  • OnWebApplicationCondition:這個是條件註解 ConditionalOnWebApplication 的判定條件,用來判斷當前系統環境是否是一個 Web 環境。
  • OnBeanCondition:這個是條件註解 @ConditionalOnBean 的判定條件,就是判斷當前系統下是否存在某個 Bean。
這裡獲取到的三個 AutoConfigurationImportFilter 過濾器其實就是上面這三個。接下來執行 filter 方法,如下:
List<String> filter(List<String> configurations)

{

long

 startTime = System.nanoTime();

 String[] candidates = StringUtils.toStringArray(configurations);

boolean

 skipped = 

false

;

for

 (AutoConfigurationImportFilter filter : 

this

.filters) {

boolean

[] match = filter.match(candidates, 

this

.autoConfigurationMetadata);

for

 (

int

 i = 

0

; i < match.length; i++) {

if

 (!match[i]) {

    candidates[i] = 

null

;

    skipped = 

true

;

   }

  }

 }

if

 (!skipped) {

return

 configurations;

 }

 List<String> result = 

new

 ArrayList<>(candidates.length);

for

 (String candidate : candidates) {

if

 (candidate != 

null

) {

   result.add(candidate);

  }

 }

return

 result;

}

這裡就是遍歷這三個過濾器,然後分別呼叫各自的 match 方法和 144 個自動化配置類進行匹配,如果這些自動化配置類所需要的條件得到滿足,則 match 陣列對應的位置就為 true,否則就為 false。
然後遍歷 match 陣列,將不滿足條件的自動化配置類置為 null,最後再把這些 null 移除掉。
這樣就獲取到了我們需要進行自動化配置的類了。
最後一句 fireAutoConfigurationImportEvents 則是觸發自動化配置類匯入事件,這個沒啥好說的~
當這些自動化配置類載入進來之後,接下來就是各種條件註解來決定這些配置類是否生效了,這些都比較簡單了,之前在 vhr 種也和小夥伴們講過多次了,這裡就不再囉嗦了~
好啦,經過上面的梳理相信小夥伴們對 Spring Boot 啟動原理有一個大概的認知了吧~

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章