SpringBoot 自動裝配原理詳解

什么是 SpringBoot 自動裝配?

我們現在提到自動裝配的時候,一般會和 Spring Boot 聯系在一起。但是,實際上 Spring Framework 早就實現了這個功能。Spring Boot 只是在其基礎上,通過 SPI 的方式,做了進一步優化。

SpringBoot 定義了一套接口規范,這套規范規定:SpringBoot 在啟動時會掃描外部引用 jar
包中的META-INF/spring.factories文件,將文件中配置的類型信息加載到 Spring 容器(此處涉及到 JVM
類加載機制與 Spring 的容器知識),并執行類中定義的各種操作。對于外部 jar 來說,只需要按照 SpringBoot
定義的標準,就能將自己的功能裝置進 SpringBoot。

沒有 Spring Boot 的情況下,如果我們需要引入第三方依賴,需要手動配置,非常麻煩。但是,Spring Boot 中,我們直接引入一個 starter 即可。比如你想要在項目中使用 redis 的話,直接在項目中引入對應的 starter 即可。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

引入 starter 之后,我們通過少量注解和一些簡單的配置就能使用第三方組件提供的功能了。在我看來,自動裝配可以簡單理解為:通過注解或者一些簡單的配置就能在 Spring Boot 的幫助下實現某塊功能。

SpringBoot 是如何實現自動裝配的?

我們先看一下 SpringBoot 的核心注解 SpringBootApplication 。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
<1.>@SpringBootConfiguration
<2.>@ComponentScan
<3.>@EnableAutoConfiguration
public @interface SpringBootApplication {}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //實際上它也是一個配置類
public @interface SpringBootConfiguration {
}

大概可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。根據 SpringBoot 官網,這三個注解的作用分別是:

  • @EnableAutoConfiguration:啟用 SpringBoot 的自動配置機制
  • @Configuration:允許在上下文中注冊額外的 bean 或導入其他配置類
  • @ComponentScan:掃描被@Component (@Service,@Controller)注解的 bean,注解默認會掃描啟動類所在的包下所有的類 ,可以自定義不掃描某些 bean。如下圖所示,容器中將排除TypeExcludeFilter和AutoConfigurationExcludeFilter。

在這里插入圖片描述
@EnableAutoConfiguration 是實現自動裝配的重要注解,我們以這個注解入手。

@EnableAutoConfiguration:實現自動裝配的核心注解

EnableAutoConfiguration 只是一個簡單地注解,自動裝配核心功能的實現實際是通過 AutoConfigurationImportSelector類。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:將main包下的所有組件注冊到容器中
@Import({AutoConfigurationImportSelector.class}) //加載自動裝配類 xxxAutoconfiguration
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

我們現在重點分析下AutoConfigurationImportSelector 類到底做了什么?

AutoConfigurationImportSelector:加載自動裝配類

AutoConfigurationImportSelector類的繼承體系如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}public interface DeferredImportSelector extends ImportSelector {}public interface ImportSelector {String[] selectImports(AnnotationMetadata var1);
}

可以看出,AutoConfigurationImportSelector 類實現了 ImportSelector接口,也就實現了這個接口中的 selectImports方法,該方法主要用于獲取所有符合條件的類的全限定類名,這些類需要被加載到 IoC 容器中。

private static final String[] NO_IMPORTS = new String[0];public String[] selectImports(AnnotationMetadata annotationMetadata) {// <1>.判斷自動裝配開關是否打開if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {//<2>.獲取所有需要裝配的beanAutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}

這里我們需要重點關注一下getAutoConfigurationEntry()方法,這個方法主要負責加載自動配置類的。

該方法調用鏈如下:

在這里插入圖片描述
現在我們結合getAutoConfigurationEntry()的源碼來詳細分析一下:

private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {//<1>.if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {//<2>.AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//<3>.List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//<4>.configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.filter(configurations, autoConfigurationMetadata);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}}
第 1 步:

判斷自動裝配開關是否打開。默認spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中設置

在這里插入圖片描述

第 2 步:

用于獲取EnableAutoConfiguration注解中的 exclude 和 excludeName。

在這里插入圖片描述

第 3 步

獲取需要自動裝配的所有配置類,讀取META-INF/spring.factories

spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

在這里插入圖片描述
從下圖可以看到這個文件的配置內容都被我們讀取到了。XXXAutoConfiguration的作用就是按需加載組件。

在這里插入圖片描述
不光是這個依賴下的META-INF/spring.factories被讀取到,所有 Spring Boot Starter 下的META-INF/spring.factories都會被讀取到。

所以,你可以清楚滴看到, druid 數據庫連接池的 Spring Boot Starter 就創建了META-INF/spring.factories文件。

如果,我們自己要創建一個 Spring Boot Starter,這一步是必不可少的。

在這里插入圖片描述

第 4 步:

到這里可能面試官會問你:“spring.factories中這么多配置,每次啟動都要全部加載么?”。很明顯,這是不現實的。我們 debug 到后面你會發現,configurations 的值變小了。

因為,這一步有經歷了一遍篩選,@ConditionalOnXXX 中的所有條件都滿足,該類才會生效。

@Configuration
// 檢查相關的類:RabbitTemplate 和 Channel是否存在
// 存在才會加載
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}

有興趣的童鞋可以詳細了解下 Spring Boot 提供的條件注解

  • @ConditionalOnBean:當容器里有指定 Bean 的條件下@ConditionalOnMissingBean:當容器里沒有指定 Bean 的情況下@ConditionalOnSingleCandidate:當指定 Bean 在容器中只有一個,或者雖然有多個但是指定首選 Bean@ConditionalOnClass:當類路徑下有指定類的條件下@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下@ConditionalOnProperty:指定的屬性是否有指定的值@ConditionalOnResource:類路徑是否有指定的值@ConditionalOnExpression:基于 SpEL 表達式作為判斷條件@ConditionalOnJava:基于 Java 版本作為判斷條件@ConditionalOnJndi:在 JNDI 存在的條件下差在指定的位置@ConditionalOnNotWebApplication:當前項目不是 Web 項目的條件下@ConditionalOnWebApplication:當前項目是 Web 項 目的條件下#

如何實現一個 Starter

現在就來擼一個 starter,實現自定義線程池

第一步,創建threadpool-spring-boot-starter工程

在這里插入圖片描述

第二步,引入 Spring Boot 相關依賴

在這里插入圖片描述

第三步,創建ThreadPoolAutoConfiguration

在這里插入圖片描述

第四步,在threadpool-spring-boot-starter工程的 resources 包下創建META-INF/spring.factories文件
在這里插入圖片描述

最后新建工程引入threadpool-spring-boot-starter

在這里插入圖片描述

測試通過!!!

在這里插入圖片描述

總結

Spring Boot 通過@EnableAutoConfiguration開啟自動裝配,通過 SpringFactoriesLoader 最終加載META-INF/spring.factories中的自動配置類實現自動裝配,自動配置類其實就是通過@Conditional按需加載的配置類,想要其生效必須引入spring-boot-starter-xxx包實現起步依賴

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/210191.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/210191.shtml
英文地址,請注明出處:http://en.pswp.cn/news/210191.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

解決npm install時報:gyp ERR! configure error

報錯內容&#xff1a; npm ERR! gyp ERR! cwd C:\Users\zccbbg\code\my\examvue\node_modules\node-sass npm ERR! gyp ERR! node -v v16.13.1 npm ERR! gyp ERR! node-gyp -v v3.8.0 npm ERR! gyp ERR! not ok npm ERR! Build failed with error code: 1 解決辦法&#xff1a;…

自行編寫一個簡單的shell!

本文旨在編寫一個簡單的shell外殼程序&#xff01;功能類似于shell的一些基本操作&#xff01;雖然不能全部實現shell的一些功能&#xff01;但是通過此文章&#xff0c;自己寫一個簡單的shell程序也是不成問題&#xff01;并且通過此文章&#xff0c;可以讓讀者對linux中一些環…

C#基礎面試題集

C#基礎 1. 簡述值類型和引用類型有什么區別2. C# String類型比 stringBuilder 類型的優勢是什么?3.面向對象的三大特點4.請簡述private&#xff0c;public&#xff0c;protected&#xff0c;internal的區別5.結構體和類6.請描述Interface與抽象類之間的不同7.在類的構造函數前…

藍橋杯:貨物擺放--因數存到數組里的技巧--減少運算量的方法

小藍有一個超大的倉庫&#xff0c;可以擺放很多貨物。 現在&#xff0c;小藍有 n 箱貨物要擺放在倉庫&#xff0c;每箱貨物都是規則的正方體。小藍規定了長、寬、高三個互相垂直的方向&#xff0c;每箱貨物的邊都必須嚴格平行于長、寬、高。 小藍希望所有的貨物最終擺成一個大…

go自帶rpc框架生產環境使用demo

基礎使用 序列化使用自帶gob協議 server package mainimport ("net""net/rpc" )// 定義一個handler結構體 type HelloService struct { }// 定義handler方法,大小寫&#xff0c;參數&#xff0c;返回值都是固定的&#xff0c;否則無法注冊 func (receiv…

數據庫事務:保障數據一致性的基石

目錄 1. 什么是數據庫事務&#xff1f; 1.1 ACID特性解析 2. 事務的實現與控制 2.1 事務的開始和結束 2.2 事務的隔離級別 3. 并發控制與事務管理 3.1 并發控制的挑戰 3.2 鎖和并發控制算法 4. 最佳實踐與性能優化 4.1 事務的劃分 4.2 批處理操作 5. 事務的未來發展…

Qt OpenCV 學習(文章鏈接匯總)

Qt OpenCV 學習&#xff08;一&#xff09;&#xff1a;環境搭建 Qt OpenCV 學習&#xff08;二&#xff09;&#xff1a;兩個簡單圖片識別案例 Qt OpenCV 學習&#xff08;三&#xff09;&#xff1a;跟蹤視頻中的運動物體 Qt OpenCV 學習&#xff08;四&#xff09;&#xff…

SpringSecurity6 | 自定義登錄頁面

?作者簡介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;熱愛Java后端開發者&#xff0c;一個想要與大家共同進步的男人&#x1f609;&#x1f609; &#x1f34e;個人主頁&#xff1a;Leo的博客 &#x1f49e;當前專欄&#xff1a; Java從入門到精通 ?特色專欄&#xf…

高工氫電年會 | 未勢能源解超朋博士受邀出席并做主題演講

12月4日&#xff0c;以“戰略重構 商業覺醒”為主題的2023高工氫電年會在深圳舉辦&#xff0c;未勢能源副總裁解超朋博士受邀出席開幕式論壇&#xff0c;以《把握機遇、直面挑戰&#xff0c;迎接氫車規模化推廣時代》為主題發表演講&#xff0c;并參與圓桌論壇研討。 氫勢已來&…

【Linux】resolv.conf 文件

resolv.conf resolv.conf 文件 是 DNS 的 client 端使用的文件&#xff0c;用于設置 DNS 服務器的 ip 地址以及 DNS 域名&#xff0c;還可以配置域名搜索順序等等。主要包含如下關鍵字&#xff1a;nameserver、domain、search、sortlist、options。設置的格式都是 關鍵字空格 …

管理類聯考——數學——真題篇——按知識分類——數據

文章目錄 排列組合2023真題&#xff08;2023-05&#xff09;-數據分析-排列組合-組合-C運算-至少-需反面思考真題&#xff08;2023-08&#xff09;-數據分析-排列組合-相鄰不相鄰-捆綁法插空法-插空法注意空位比座位多1個&#xff0c;是用A&#xff1b;捆綁法內部排序用A&#…

Linux(centos, ubuntu) 快速安裝anaconda;5秒安裝anaconda

1.下載Anaconda安裝腳本: 首先&#xff0c;訪問Anaconda的官方下載頁面&#xff1a;https://www.anaconda.com/products/distribution 在頁面上&#xff0c;選擇適用于Linux的Python 3.x版本的Anaconda安裝腳本。也可以使用wget或curl命令從終端下載。示例&#xff1a; wget …

2023中國(海南)國際高爾夫旅游文化博覽會 暨國際商界峰層·全球華人高爾夫精英巡回賽 全國潁商自貿港行盛大啟幕

2023中國&#xff08;海南&#xff09;國際高爾夫旅游文化博覽會&#xff08;以下簡稱“海高博”&#xff09;暨全國潁商走進海南自貿港于12月7-9日在海口觀瀾湖盛大開幕。該活動由中國國際貿易促進委員會海南省委員會、海南省旅游和文化廣電體育廳主辦&#xff0c;中國國際商會…

C語言中getchar函數

在 C 語言中&#xff0c;getchar() 是一個標準庫函數&#xff0c;用于從標準輸入&#xff08;通常是鍵盤&#xff09;讀取單個字符。它的函數原型如下&#xff1a; int getchar(void);getchar() 函數的工作原理如下&#xff1a; 當調用 getchar() 函數時&#xff0c;它會等待…

最新版本11.17的YOLOv8加入注意力方法

本文基于11.17版本,以往版本略有不同,可查看改進YOLOv8,教你YOLOv8如何添加20多種注意力機制進行參考 放入注意力代碼,以biformer注意力為例 import torch import torch.nn as nn import torch.nn.functional as Fdef position(H, W, is_cuda=

探索 Python 中鏈表的實現:從基礎到高級

# 更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com 鏈表是一種基礎的數據結構&#xff0c;它由一系列節點組成&#xff0c;每個節點都包含數據和指向下一個節點的引用。在Python中&#xff0c;可以使用類來實現鏈表&#xff0c;本文將介紹如何實現鏈表&#xff…

c語言編程題經典100例——(90~95例)

1,寫一個函數&#xff0c;實現數字的加密和解密。 下面是一個簡單的C語言函數&#xff0c;可以實現數字的加密和解密。這個函數采用簡單的加密算法&#xff0c;將輸入的數字乘以一個固定的密鑰&#xff0c;然后加上一個固定的偏移量。解密過程就是將加密后的數字減去偏移量&am…

《C++新經典設計模式》之第18章 備忘錄模式

《C新經典設計模式》之第18章 備忘錄模式 備忘錄模式.cpp 備忘錄模式.cpp #include <iostream> #include <vector> #include <memory> using namespace std;// 保存對象內部狀態&#xff0c;必要時恢復 // 在不破壞封裝性的前提下&#xff0c;捕獲對象的內部…

(C)一些題11

1. #include<stdio.h> #include<string.h> void main() { char *s1"ABCDEF"&#xff0c;*s2"aB"&#xff1b; s1; s2; puts(s1)&#xff1b; puts(s2)&#xff1b; printf("%d\n",strcmp(s1,s2))&#xff1b; } 答案&#xff1…

【密碼學引論】認證

認證是許多應用系統中安全保護的第一道設防認證和加密的區別&#xff1a;加密用來確保數據的保密性&#xff0c;而認證用來確保報文發送者和接受者的真實性和報文的完整性。認證和數字簽名的區別&#xff1a; 認證總是基于某種收發雙方共享的保密數據來認證被鑒別對象的真實性&…