解析SpringBoot @EnableAutoConfiguration的使用
剛做后端開發(fā)的時候,最早接觸的是基礎(chǔ)的spring,為了引用二方包提供bean,還需要在xml中增加對應(yīng)的包<context:component-scan base-package="xxx" /> 或者增加注解@ComponentScan({ "xxx"})。當(dāng)時覺得挺urgly的,但也沒有去研究有沒有更好的方式。
直到接觸Spring Boot 后,發(fā)現(xiàn)其可以自動引入二方包的bean。不過一直沒有看這塊的實現(xiàn)原理。直到最近面試的時候被問到。所以就看了下實現(xiàn)邏輯。
使用姿勢
講原理前先說下使用姿勢。
在project A中定義一個bean。
package com.wangzhi;
import org.springframework.stereotype.Service;
@Service
public class Dog {
}
并在該project的resources/META-INF/下創(chuàng)建一個叫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
原理解析
總體分為兩個部分:一是收集所有spring.factories中EnableAutoConfiguration相關(guān)bean的類,二是將得到的類注冊到spring容器中。
收集bean定義類
在spring容器啟動時,會調(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會調(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();
// 屬性文件中可以用','分割多個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);
}
}
注冊到容器
在上面的流程中得到了所有在spring.factories中指定的bean的類路徑,在processGroupImports方法中會以處理@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類型其處理邏輯會不一樣,這里不關(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類定義,最終會被以Configuration一樣的處理方式注冊到容器中。
End
@EnableAutoConfiguration注解簡化了導(dǎo)入了二方包bean的成本。提供一個二方包給其他應(yīng)用使用,只需要在二方包里將對外暴露的bean定義在spring.factories中就好了。對于不需要的bean,可以在使用方用@EnableAutoConfiguration的exclude屬性進(jìn)行排除。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot自動配置@EnableAutoConfiguration過程示例
- SpringBoot中的@EnableAutoConfiguration注解解析
- Springboot注解之@EnableAutoConfiguration詳解
- SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別
- SpringBoot中@EnableAutoConfiguration注解源碼分析
- SpringBoot使用@EnableAutoConfiguration實現(xiàn)自動配置詳解
- SpringBoot中@EnableAutoConfiguration注解的實現(xiàn)
相關(guān)文章
SpringBoot中注解實現(xiàn)定時任務(wù)的兩種方式
這篇文章主要介紹了SpringBoot中注解實現(xiàn)定時任務(wù)的兩種方式,SpringBoot 定時任務(wù)是一種在SpringBoot應(yīng)用中自動執(zhí)行任務(wù)的機(jī)制,通過使用Spring框架提供的@Scheduled注解,我們可以輕松地創(chuàng)建定時任務(wù),需要的朋友可以參考下2023-10-10
Java中的system.getProperty()的作用及使用方法
System.getProperty()?方法用于獲取系統(tǒng)屬性的值,該方法接受一個字符串參數(shù),表示要獲取的系統(tǒng)屬性的名稱,返回值為字符串類型,表示該屬性的值,接下來通過本文給大家介紹Java中的system.getProperty()的作用及使用方法,感興趣的朋友跟隨小編一起看看吧2023-05-05
淺談JAVA字符串匹配算法indexOf函數(shù)的實現(xiàn)方法
這篇文章主要介紹了淺談字符串匹配算法indexOf函數(shù)的實現(xiàn)方法,indexOf函數(shù)我們可以查找一個字符串(模式串)是否在另一個字符串(主串)出現(xiàn)過。對此感興趣的可以來了解一下2020-07-07
SpringBoot使用Validation進(jìn)行參數(shù)校驗的示例詳解
在 SpringBoot項目開發(fā)中,有一個觀點是不要相信前端傳入的參數(shù),因為你不知道用戶是怎么操作我們接口的,所以在后端也需要對參數(shù)進(jìn)行校驗,這篇文章主要講講我們項目中最常使用的驗證方案2023-05-05
dockerfile-maven-plugin極簡教程(推薦)
這篇文章主要介紹了dockerfile-maven-plugin極簡教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
java 遍歷request中的所有表單數(shù)據(jù)的實例代碼
下面小編就為大家?guī)硪黄猨ava 遍歷request中的所有表單數(shù)據(jù)的實例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09
Quarkus的Spring擴(kuò)展快速改造Spring項目
這篇文章主要為大家介紹了Quarkus的Spring項目擴(kuò)展,帶大家快速改造Spring項目示例演繹,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02
Java設(shè)計模式以虹貓藍(lán)兔的故事講解代理模式
代理模式是Java常見的設(shè)計模式之一。所謂代理模式是指客戶端并不直接調(diào)用實際的對象,而是通過調(diào)用代理,來間接的調(diào)用實際的對象2022-04-04
Intellij Idea 多模塊Maven工程中模塊之間無法相互引用問題
這篇文章主要介紹了Intellij Idea 多模塊Maven工程中模塊之間無法相互引用問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

