springboot自動(dòng)裝配的源碼與流程圖
前言
在使用SpringBoot開(kāi)發(fā)項(xiàng)目中,遇到一些 XXX-XXX-starter,例如mybatis-plus-boot-starter,這些包總是能夠自動(dòng)進(jìn)行配置,
減少了開(kāi)發(fā)人員配置一些項(xiàng)目配置的時(shí)間,讓開(kāi)發(fā)者擁有更多的時(shí)間用于開(kāi)發(fā)的任務(wù)上面。下面從源碼開(kāi)始。
正文
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)寫(xiě)在代碼中的了,這里面最重要的是getCandidateConfigurations方法,其次是下面的過(guò)濾排除不需要的配置信息。下面進(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方法就是通過(guò)傳入factoryType(也就是calss.name)來(lái)獲取數(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í)從源碼上來(lái)看其實(shí)也不難。
大概流程圖如下:
最后
說(shuō)了這么多源碼,也畫(huà)了流程圖。這里面最重要的就是SpringFactoriesLoader,從這個(gè)類名就可以看出來(lái),是專門(mén)處理factories文件的,這個(gè)類只提供了兩個(gè)靜態(tài)方法,而EnableAutoConfiguration只是取了其中的EnableAutoConfiguration下的數(shù)據(jù),那么其它的數(shù)據(jù)呢,不會(huì)用到嗎?肯定不會(huì)的,所以spring在很多地方會(huì)用到這個(gè)類,后面看spring源碼的時(shí)候在來(lái)看吧,這里先標(biāo)記一下。
還有就是,SpringFactoriesLoader和JDK的SPI也是差不多的一個(gè)思想。下一篇就來(lái)看看JDK的SPI
到此這篇關(guān)于springboot自動(dòng)裝配的源碼與流程圖的文章就介紹到這了,更多相關(guān)springboot自動(dòng)裝配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 配置MySQL數(shù)據(jù)庫(kù)重連的操作方法
這篇文章主要介紹了Spring Boot 配置MySQL數(shù)據(jù)庫(kù)重連的操作方法,需要的朋友可以參考下2018-04-04SpringMVC實(shí)現(xiàn)文件上傳和下載功能
這篇文章主要為大家詳細(xì)介紹了SpringMVC實(shí)現(xiàn)文件上傳和下載功能 ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08SpringBoot導(dǎo)入導(dǎo)出數(shù)據(jù)實(shí)現(xiàn)方法詳解
這篇文章主要介紹了SpringBoot導(dǎo)入導(dǎo)出數(shù)據(jù)實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12Java中JSON對(duì)象字段為null值的顯示處理方法
這篇文章主要給大家介紹了關(guān)于Java中JSON對(duì)象字段為null值的顯示處理方法,最近開(kāi)發(fā)過(guò)程中前端反應(yīng)后臺(tái)返回的json中包含null,不好處理,這里介紹下,需要的朋友可以參考下2023-08-08SpringBoot實(shí)現(xiàn)登錄攔截器的方法詳解
其實(shí)spring?boot攔截器的配置方式和springMVC差不多,只有一些小的改變需要注意下就ok了。本文主要給大家介紹了關(guān)于如何在Springboot實(shí)現(xiàn)登陸攔截器功能,需要的朋友可以參考下2022-07-07SpringBoot實(shí)現(xiàn)前端驗(yàn)證碼圖片生成和校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)前端驗(yàn)證碼圖片生成和校驗(yàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02mybatis多個(gè)區(qū)間處理方式(雙foreach循環(huán))
這篇文章主要介紹了mybatis多個(gè)區(qū)間處理方式(雙foreach循環(huán)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02