解析SpringBoot @EnableAutoConfiguration的使用
剛做后端開發(fā)的時(shí)候,最早接觸的是基礎(chǔ)的spring,為了引用二方包提供bean,還需要在xml中增加對(duì)應(yīng)的包<context:component-scan base-package="xxx" /> 或者增加注解@ComponentScan({ "xxx"})。當(dāng)時(shí)覺得挺urgly的,但也沒有去研究有沒有更好的方式。
直到接觸Spring Boot 后,發(fā)現(xiàn)其可以自動(dòng)引入二方包的bean。不過一直沒有看這塊的實(shí)現(xiàn)原理。直到最近面試的時(shí)候被問到。所以就看了下實(shí)現(xiàn)邏輯。
使用姿勢(shì)
講原理前先說下使用姿勢(shì)。
在project A中定義一個(gè)bean。
package com.wangzhi; import org.springframework.stereotype.Service; @Service public class Dog { }
并在該project的resources/META-INF/下創(chuàng)建一個(gè)叫spring.factories的文件,該文件內(nèi)容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
然后在project B中引用project A的jar包。
projectA代碼如下:
package com.wangzhi.springbootdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; @EnableAutoConfiguration public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(context.getBean(com.wangzhi.Dog.class)); } }
打印結(jié)果:
com.wangzhi.Dog@3148f668
原理解析
總體分為兩個(gè)部分:一是收集所有spring.factories中EnableAutoConfiguration相關(guān)bean的類,二是將得到的類注冊(cè)到spring容器中。
收集bean定義類
在spring容器啟動(dòng)時(shí),會(huì)調(diào)用到AutoConfigurationImportSelector#getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // EnableAutoConfiguration注解的屬性:exclude,excludeName等 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 得到所有的Configurations List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重 configurations = removeDuplicates(configurations); // 刪除掉exclude中指定的類 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
getCandidateConfigurations會(huì)調(diào)用到方法loadFactoryNames:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // factoryClassName為org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryClassName = factoryClass.getName(); // 該方法返回的是所有spring.factories文件中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的類路徑 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 找到所有的"META-INF/spring.factories" Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); // 讀取文件內(nèi)容,properties類似于HashMap,包含了屬性的key和value Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); // 屬性文件中可以用','分割多個(gè)value for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
注冊(cè)到容器
在上面的流程中得到了所有在spring.factories中指定的bean的類路徑,在processGroupImports方法中會(huì)以處理@import注解一樣的邏輯將其導(dǎo)入進(jìn)容器。
public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { // getImports即上面得到的所有類路徑的封裝 grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get( entry.getMetadata()); try { // 和處理@Import注解一樣 processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { ... // 遍歷收集到的類路徑 for (SourceClass candidate : importCandidates) { ... //如果candidate是ImportSelector或ImportBeanDefinitionRegistrar類型其處理邏輯會(huì)不一樣,這里不關(guān)注 // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // 當(dāng)作 @Configuration 處理 processConfigurationClass(candidate.asConfigClass(configClass)); ... } ... }
可以看到,在第一步收集的bean類定義,最終會(huì)被以Configuration一樣的處理方式注冊(cè)到容器中。
End
@EnableAutoConfiguration注解簡化了導(dǎo)入了二方包bean的成本。提供一個(gè)二方包給其他應(yīng)用使用,只需要在二方包里將對(duì)外暴露的bean定義在spring.factories中就好了。對(duì)于不需要的bean,可以在使用方用@EnableAutoConfiguration的exclude屬性進(jìn)行排除。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot自動(dòng)配置@EnableAutoConfiguration過程示例
- SpringBoot中的@EnableAutoConfiguration注解解析
- Springboot注解之@EnableAutoConfiguration詳解
- SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別
- SpringBoot中@EnableAutoConfiguration注解源碼分析
- SpringBoot使用@EnableAutoConfiguration實(shí)現(xiàn)自動(dòng)配置詳解
- SpringBoot中@EnableAutoConfiguration注解的實(shí)現(xiàn)
相關(guān)文章
SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式
這篇文章主要介紹了SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式,SpringBoot 定時(shí)任務(wù)是一種在SpringBoot應(yīng)用中自動(dòng)執(zhí)行任務(wù)的機(jī)制,通過使用Spring框架提供的@Scheduled注解,我們可以輕松地創(chuàng)建定時(shí)任務(wù),需要的朋友可以參考下2023-10-10Java中的system.getProperty()的作用及使用方法
System.getProperty()?方法用于獲取系統(tǒng)屬性的值,該方法接受一個(gè)字符串參數(shù),表示要獲取的系統(tǒng)屬性的名稱,返回值為字符串類型,表示該屬性的值,接下來通過本文給大家介紹Java中的system.getProperty()的作用及使用方法,感興趣的朋友跟隨小編一起看看吧2023-05-05淺談JAVA字符串匹配算法indexOf函數(shù)的實(shí)現(xiàn)方法
這篇文章主要介紹了淺談字符串匹配算法indexOf函數(shù)的實(shí)現(xiàn)方法,indexOf函數(shù)我們可以查找一個(gè)字符串(模式串)是否在另一個(gè)字符串(主串)出現(xiàn)過。對(duì)此感興趣的可以來了解一下2020-07-07SpringBoot使用Validation進(jìn)行參數(shù)校驗(yàn)的示例詳解
在 SpringBoot項(xiàng)目開發(fā)中,有一個(gè)觀點(diǎn)是不要相信前端傳入的參數(shù),因?yàn)槟悴恢烙脩羰窃趺床僮魑覀兘涌诘?,所以在后端也需要?duì)參數(shù)進(jìn)行校驗(yàn),這篇文章主要講講我們項(xiàng)目中最常使用的驗(yàn)證方案2023-05-05dockerfile-maven-plugin極簡教程(推薦)
這篇文章主要介紹了dockerfile-maven-plugin極簡教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10java 遍歷request中的所有表單數(shù)據(jù)的實(shí)例代碼
下面小編就為大家?guī)硪黄猨ava 遍歷request中的所有表單數(shù)據(jù)的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09Quarkus的Spring擴(kuò)展快速改造Spring項(xiàng)目
這篇文章主要為大家介紹了Quarkus的Spring項(xiàng)目擴(kuò)展,帶大家快速改造Spring項(xiàng)目示例演繹,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02Java設(shè)計(jì)模式以虹貓藍(lán)兔的故事講解代理模式
代理模式是Java常見的設(shè)計(jì)模式之一。所謂代理模式是指客戶端并不直接調(diào)用實(shí)際的對(duì)象,而是通過調(diào)用代理,來間接的調(diào)用實(shí)際的對(duì)象2022-04-04Intellij Idea 多模塊Maven工程中模塊之間無法相互引用問題
這篇文章主要介紹了Intellij Idea 多模塊Maven工程中模塊之間無法相互引用問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01