springboot自動(dòng)裝配的源碼與流程圖
前言
在使用SpringBoot開發(fā)項(xiàng)目中,遇到一些 XXX-XXX-starter,例如mybatis-plus-boot-starter,這些包總是能夠自動(dòng)進(jìn)行配置,
減少了開發(fā)人員配置一些項(xiàng)目配置的時(shí)間,讓開發(fā)者擁有更多的時(shí)間用于開發(fā)的任務(wù)上面。下面從源碼開始。
正文
SpringBoot版本:2.5.3
- 從@SpringBootApplication進(jìn)入@EnableAutoConfiguration
- 然后進(jìn)入AutoConfigurationImportSelector
@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 {}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
進(jìn)入AutoConfigurationImportSelector,可以發(fā)現(xiàn)該類是ImportSelector接口的實(shí)現(xiàn)類,然后直接定位至selectImports方法。
到了這里,其實(shí)主動(dòng)裝配的套路就是@EnableXXX加@Import的套路。這就是一個(gè)大概的認(rèn)知了。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
下面進(jìn)入getAutoConfigurationEntry方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 獲取注解信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲取自動(dòng)配置的信息
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);
return new AutoConfigurationEntry(configurations, exclusions);
}
詳細(xì)的注釋已經(jīng)寫在代碼中的了,這里面最重要的是getCandidateConfigurations方法,其次是下面的過濾排除不需要的配置信息。下面進(jìn)入getCandidateConfigurations方法。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 重要
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
這里需要關(guān)注的方法是SpringFactoriesLoader.loadFactoryNames,進(jìn)入該方法,該方法是一個(gè)靜態(tài)方法。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 獲取類名
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 查詢緩存
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 1. 獲取類加載器能讀取的所有在META-INF目錄下的spring.factories文件
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
// 遍歷路徑
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 將路徑下的文件數(shù)據(jù)讀取為Properties
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 遍歷Properties
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);
}
return result;
}
- loadSpringFactories方法就是加載并讀取所傳入的類加載器能讀取的所有spring.factories文件,并將讀取的數(shù)據(jù)最終轉(zhuǎn)換為Map<String, List>類型的數(shù)據(jù),然后存入本地緩存。
- loadFactoryNames方法就是通過傳入factoryType(也就是calss.name)來獲取數(shù)據(jù),并不是獲取所有的數(shù)據(jù)。所以這里會(huì)有很多地方會(huì)用到。
- @EnableAutoConfiguration是取的文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以一般自定義starter的自動(dòng)配置文件都是在這個(gè)key后面。例如:org.springframework.boot.autoconfigure.EnableAutoConfiguration= a.b.c.d.XXX;
至此,源碼就差不多完成了,其實(shí)從源碼上來看其實(shí)也不難。
大概流程圖如下:

最后
說了這么多源碼,也畫了流程圖。這里面最重要的就是SpringFactoriesLoader,從這個(gè)類名就可以看出來,是專門處理factories文件的,這個(gè)類只提供了兩個(gè)靜態(tài)方法,而EnableAutoConfiguration只是取了其中的EnableAutoConfiguration下的數(shù)據(jù),那么其它的數(shù)據(jù)呢,不會(huì)用到嗎?肯定不會(huì)的,所以spring在很多地方會(huì)用到這個(gè)類,后面看spring源碼的時(shí)候在來看吧,這里先標(biāo)記一下。
還有就是,SpringFactoriesLoader和JDK的SPI也是差不多的一個(gè)思想。下一篇就來看看JDK的SPI
到此這篇關(guān)于springboot自動(dòng)裝配的源碼與流程圖的文章就介紹到這了,更多相關(guān)springboot自動(dòng)裝配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java二叉樹的幾種遍歷遞歸與非遞歸實(shí)現(xiàn)代碼
這篇文章主要介紹了java二叉樹的幾種遍歷遞歸與非遞歸實(shí)現(xiàn)代碼,需要的朋友可以參考下2020-12-12
Springboot集成RabbitMQ報(bào)錯(cuò)及解決
這篇文章主要介紹了Springboot集成RabbitMQ報(bào)錯(cuò)及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
IDEA啟動(dòng)報(bào)錯(cuò)Internal?error.?Please?refer?to?https://jb.gg/i
這篇文章主要介紹了IDEA啟動(dòng)報(bào)錯(cuò)Internal?error.?Please?refer?to?https://jb.gg/ide/critical-startup-errors解決辦法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
java 簡(jiǎn)單的計(jì)算器程序?qū)嵗a
這篇文章主要介紹了java 簡(jiǎn)單的計(jì)算器程序?qū)嵗a的相關(guān)資料,需要的朋友可以參考下2017-06-06
基于SpringMVC @RequestMapping的參數(shù)和用法
這篇文章主要介紹了SpringMVC @RequestMapping的參數(shù)和用法解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
深入解析Java的設(shè)計(jì)模式編程中建造者模式的運(yùn)用
這篇文章主要介紹了深入解析Java的設(shè)計(jì)模式編程中建造者模式的運(yùn)用,同時(shí)文中也介紹了建造者模式與工廠模式的區(qū)別,需要的朋友可以參考下2016-02-02
Java可重入鎖的實(shí)現(xiàn)原理與應(yīng)用場(chǎng)景
今天小編就為大家分享一篇關(guān)于Java可重入鎖的實(shí)現(xiàn)原理與應(yīng)用場(chǎng)景,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01
IDEA遠(yuǎn)程連接HBase及其Java API實(shí)戰(zhàn)詳解
這篇文章主要介紹了IDEA遠(yuǎn)程連接HBase及其Java API實(shí)戰(zhàn)詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
SpringBoot使用Redis實(shí)現(xiàn)消息隊(duì)列的方法小結(jié)
在應(yīng)用中把Redis當(dāng)成消息隊(duì)列來使用已經(jīng)屢見不鮮了,我想主要原因是當(dāng)代應(yīng)用十有八九都會(huì)用到 Redis,因此不用再引入其他消息隊(duì)列系統(tǒng),而且Redis提供了好幾種實(shí)現(xiàn)消息隊(duì)列的方法,用起來也簡(jiǎn)單,本文給大家介紹了SpringBoot使用Redis實(shí)現(xiàn)消息隊(duì)列的方法小結(jié)2024-04-04

