SpringBoot自動(dòng)裝配原理詳解
首先對(duì)于一個(gè)SpringBoot工程來(lái)說(shuō),最明顯的標(biāo)志的就是
@SpringBootApplication它標(biāo)記了這是一個(gè)SpringBoot工程,所以今天的 SpringBoot自動(dòng)裝配原理也就是從它開(kāi)始說(shuō)起。
自動(dòng)裝配流程
首先我們來(lái)看下@SpringBootApplication 這個(gè)注解的背后又有什么玄機(jī)呢,我們按下 ctrl + 鼠標(biāo)左鍵,輕輕的點(diǎn)一下,此時(shí)見(jiàn)證奇跡的時(shí)刻..
我們看到如下優(yōu)雅的代碼:

這其中有兩個(gè)比較容易引起我們注意的地方,一個(gè)是@SpringBootConfiguration注解,另一個(gè)是@EnableAutoConfiguration注解;之所以說(shuō)這個(gè)兩個(gè)注解比較吸引我們的眼球, 不是因?yàn)樗鼈冮L(zhǎng)大的好看,而是因?yàn)槠渌淖⒔馓y看了(主要是因?yàn)槠渌淖⒔馕覀兌际潜容^熟悉,即使不知道他們是干什么的,可以肯定更自動(dòng)裝配是沒(méi)有關(guān)系的)。 然后我們又伸出了邪惡的小手,開(kāi)啟了熟悉的操作,按下了Ctrt + 鼠標(biāo)左鍵,瞪著色咪咪的小眼睛,瞳孔放大了百倍等待著奇跡的出現(xiàn)... 擦... 擦...擦...

什么也沒(méi)有...
那我要你有何用,這么頂級(jí)的世界級(jí)的開(kāi)源項(xiàng)目,怎么會(huì)讓一個(gè)沒(méi)用的家伙存在呢? 于是動(dòng)用了上億的腦細(xì)胞大軍,經(jīng)過(guò)復(fù)雜的運(yùn)算,得出了一個(gè)不靠譜的結(jié)論:它可能使用來(lái)標(biāo)記這是一個(gè)SpringBoot工程的配置。因?yàn)?code>SpringBootConfiguration翻譯過(guò)來(lái)就是SpringBoot的配置,于是心中又是幾萬(wàn)只羊駝在萬(wàn)馬奔騰,大漠飛揚(yáng)。
氣定神閑之后,秉承著·失敗是成功之母"的信念, 熟練的左手行云流水般的按下了 Ctrl + Table 鍵,回到了最初的的地方。眼睛盯著 @EnableAutoConfiguration ,環(huán)顧左右,在地址欄輸入了谷歌翻譯, 結(jié)果顯示 自動(dòng)裝配。我找的就是你,真是眾里尋他千百度,那人卻在燈火闌珊處。 熟練的按下了 Ctrl +左鍵,迫不及待的想要進(jìn)入; 心里默默背誦起了《桃花源記》的經(jīng)典詩(shī)句 ∶
林盡水源,便得一山,山有小口,仿佛若有光。便舍船,從口入。初極狹,才通人。復(fù)行數(shù)十步,豁然開(kāi)朗

此時(shí)此刻心情愉悅,有過(guò)前面的經(jīng)歷之后,在面對(duì)新的世界時(shí)候,我們淡定了許多。 此時(shí)大腦高速運(yùn)轉(zhuǎn),沒(méi)有再糾結(jié),直搗黃龍,進(jìn)入了 AutoConfigurationImportSelector.class 類(lèi),因?yàn)楣雀璺g告訴我們,這個(gè)是自動(dòng)配置導(dǎo)入選擇器。 于是我們發(fā)現(xiàn)了—片新天地
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 獲取自動(dòng)配置的實(shí)體
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// 具體用來(lái)加載自動(dòng)配置類(lèi)得方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲取候選的配置類(lèi),即使后宮佳麗三千,也是要篩選的
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 根據(jù)情況,自動(dòng)配置需要的配置類(lèi)和不需要的配置了
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, );
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回最終需要的配置
return new AutoConfigurationEntry(configurations, exclusions);
}
}
而這個(gè)自動(dòng)配置的實(shí)體 AutoConfigurationEntry里面有兩個(gè)屬性,configurations和 exclusions。
protected static class AutoConfigurationEntry {
// 用來(lái)存儲(chǔ)需要的配置項(xiàng)
private final List<String> configurations;
// 用來(lái)存儲(chǔ)排除的配置項(xiàng)
private final Set<String> exclusions;
private AutoConfigurationEntry() {
this.configurations = Collections.emptyList();
this.exclusions = Collections.emptySet();
}
}
在后面可以看到 getAutoConfigurationEntry()方法返回了一個(gè)對(duì)象 return new AutoConfigurationEntry(configurations, exclusions);這里也就是把我們需要的配置都拿到了。
那他是怎么拿到的候選的配置類(lèi)呢? 我們接著看這個(gè)獲取候選配置類(lèi)的方法 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);進(jìn)到方法后我們看到下面這個(gè)方法具體獲取候選配置類(lèi)的方法內(nèi)容

這里我們跟著斷點(diǎn)去走,首先進(jìn)入getSpringFactoriesLoaderFactoryClass()方法
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
// 返回的是EnableAutoConfiguration字節(jié)碼對(duì)象
return EnableAutoConfiguration.class;
}
接著我們?cè)谶M(jìn)入getBeanClassLoader()方法,這里就是一個(gè)類(lèi)加載器
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
最后我們?cè)谶M(jìn)入loadFactoryNames()方法,這個(gè)方法就是根據(jù)剛才的字節(jié)碼文件和類(lèi)加載器來(lái)找到候選的配置類(lèi)。傳遞過(guò)來(lái)的字節(jié)碼
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 獲取的EnableAutoConfiguration.class的權(quán)限定名
//org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
如下圖:

最后通過(guò)loadSpringFactories()來(lái)獲取到所有的配置類(lèi)
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 緩存加載的配置類(lèi)
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 去資源目錄下找
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 加載完成放到緩存中
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
// 返回加載到的配置類(lèi)
return result;
}
這里我們要看下怎么從資源目錄下 FACTORIES_RESOURCE_LOCATION 加載的。下面是加載配置文件的路徑:

也就是項(xiàng)目啟動(dòng)的時(shí)候會(huì)去加載所有 META-INF 下的所有的 spring.factories 文件,我們搜一下這個(gè)這個(gè)文件,我搭建的是一個(gè)很簡(jiǎn)單的 SpringBoot 工程,它會(huì)去這幾個(gè) jar 里面找相關(guān)的配置類(lèi)

但是最后自動(dòng)裝配的類(lèi)是這個(gè)spring-boot-autoconfigure-2.4.3.RELEASE.jar

而根據(jù)EnabLeAutoConfiguration.class字節(jié)碼加載的配置類(lèi)就只有這118自動(dòng)配置類(lèi)

小結(jié)
實(shí)際上SpringBoot的自動(dòng)裝配原理,其實(shí)就是在項(xiàng)目啟動(dòng)的時(shí)候去加載META-INF下的 spring.factories 文件,好像也沒(méi)有那么高大上。當(dāng)然在啟動(dòng)的過(guò)程中還會(huì)有其他的配置項(xiàng)的加載,這里咱么直說(shuō)了自動(dòng)裝配的加載過(guò)程。希望對(duì)大家可以有所啟發(fā)。
以上就是SpringBoot自動(dòng)裝配原理詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot自動(dòng)裝配原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 深入淺析SpringBoot中的自動(dòng)裝配
- SpringBoot啟動(dòng)及自動(dòng)裝配原理過(guò)程詳解
- springboot?無(wú)法自動(dòng)裝配的問(wèn)題
- SpringBoot(cloud)自動(dòng)裝配bean找不到類(lèi)型的問(wèn)題
- 淺談springboot自動(dòng)裝配原理
- Springboot自動(dòng)裝配實(shí)現(xiàn)過(guò)程代碼實(shí)例
- 淺析SpringBoot自動(dòng)裝配的實(shí)現(xiàn)
- Java Springboot自動(dòng)裝配原理詳解
- java中SpringBoot?自動(dòng)裝配的原理分析
相關(guān)文章
scala當(dāng)中的文件操作和網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法
這篇文章主要介紹了scala當(dāng)中的文件操作和網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
使用Filter過(guò)濾器中訪問(wèn)getSession()要轉(zhuǎn)化
這篇文章主要介紹了使用Filter過(guò)濾器中訪問(wèn)getSession()要轉(zhuǎn)化,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringBoot實(shí)現(xiàn)類(lèi)似鉤子函數(shù)的方法
這篇文章主要給大家介紹了關(guān)于SpringBoot實(shí)現(xiàn)類(lèi)似鉤子函數(shù)的方法,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-04-04
Java詳解ScriptEngine接口動(dòng)態(tài)執(zhí)行JS腳本
ScriptEngine是基本接口,其方法必須在本規(guī)范的每個(gè)實(shí)現(xiàn)中完全起作用。這些方法提供基本腳本功能。 寫(xiě)入這個(gè)簡(jiǎn)單接口的應(yīng)用程序可以在每個(gè)實(shí)現(xiàn)中進(jìn)行最少的修改。 它包括執(zhí)行腳本的方法,以及設(shè)置和獲取值的方法2022-08-08
Java 采用反射獲取class屬性值的實(shí)現(xiàn)代碼
以下是對(duì)在Java中采用反射獲取class屬性值的實(shí)現(xiàn)代碼進(jìn)行了分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-08-08
詳解Java的readBytes是怎么實(shí)現(xiàn)的
眾所周知,Java是一門(mén)跨平臺(tái)語(yǔ)言,針對(duì)不同的操作系統(tǒng)有不同的實(shí)現(xiàn),下面小編就來(lái)從一個(gè)非常簡(jiǎn)單的api調(diào)用帶大家來(lái)看看Java具體是怎么做的吧2023-07-07
eclipse 如何創(chuàng)建 user library 方法詳解
這篇文章主要介紹了eclipse 如何創(chuàng)建 user library 方法詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04

