關于SpringBoot的自動裝配原理詳解
一、@SpringBootApplication
正常情況下的啟動類都會加上@SpringBootApplication注解
@SpringBootApplication
public class SpringbootSourceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSourceApplication.class, args);
}
}我們查看下@SpringBootApplication注解
@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
}我們可以看到有@EnableAutoConfiguration注解,這個是自動裝配的核心
二、@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}其中有兩個注解
@AutoConfigurationPackage
這個注解的作用就是將注解標記的類所在包及所有子包下的組件到掃描到Spring容器中
@Import(AutoConfigurationImportSelector.class)- @Import根據配置內容有三種使用方式
- class數組
- 將數組內的類注入到Spring容器中,bean名稱是全類名
ImportSelector類型- 實現
ImportSelector接口
- 實現
- class數組
- @Import根據配置內容有三種使用方式
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}返回值:就是我們實際需要注入到容器的組件全類名(返回值可以是空數組,但是不能為null,否則會有空指針異常)
參數:當前被@Import注解標記的所有注解信息
ImportBeanDefinitionRegistrar類型
實現ImportBeanDefinitionRegistrar接口,例如
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//指定bean定義信息(包括bean的類型、作用域...)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(自己的類.class);
//注冊一個bean指定bean名字(id)
beanDefinitionRegistry.registerBeanDefinition("自定義名稱",rootBeanDefinition);
}
}可以自定義一個或多個bean
這里采用的是第二種方式,通過AutoConfigurationImportSelector來返回需要注入的類名數組
三、AutoConfigurationImportSelector
我們看下AutoConfigurationImportSelector類中selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//1、加載spring-autoconfigure-metadata.properties信息,并保存到AutoConfigurationMetadata實例中
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//2、獲取自動裝配信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}這個方法有兩步
- 第一步:加載
spring-autoconfigure-metadata.properties信息,并保存到AutoConfigurationMetadata實例中 - 第二步:獲取自動裝配信息
1、加載spring-autoconfigure-metadata.properties信息
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}邏輯很簡單,就是加載spring-autoconfigure-metadata.properties并封裝到AutoConfigurationMetadata中。spring-autoconfigure-metadata.properties存儲了自動裝配的條件元信息,例如
#...省略... org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.ConditionalOnBean=javax.jms.ConnectionFactory #...省略...
格式:全類名.條件=條件值
條件(列舉下面三個常見的)
ConditionalOnClass:classpath下存在某個類才會加載ConditionalOnBean:容器內下存在某個bean才會加載ConditionalOnWebApplication:在什么樣的web環(huán)境下才會加載
2、獲取自動裝配的類信息
這里的消息包括兩個:最終需要自動裝配的類列表,和被排除的類列表
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
//1、判斷是否允許自動裝配,如果否則直接返回空對象
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//2、獲取@EnableAutoConfiguration注解上標注的類的元信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//3、獲取自動裝配的候選類名集合
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//4、可能存在重復,所以這里進行去重(因為是從配置文件類名獲取的,可能會配置重,所有先去下重)
configurations = removeDuplicates(configurations);
//5、獲取自動裝配組件的排除名單
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//6、檢查排除名單是否合法
checkExcludedClasses(configurations, exclusions);
//7、排除exclusions中的類
configurations.removeAll(exclusions);
//8、執(zhí)行過濾操作,依賴前面AutoConfigurationMetadataLoader.loadMetadata獲取的autoConfigurationMetadata
configurations = filter(configurations, autoConfigurationMetadata);
//9、觸發(fā)自動裝配的導入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
//10、返回信息
return new AutoConfigurationEntry(configurations, exclusions);
}分為一下10個步驟
2.1)判斷是否允許自動裝配,如果否則直接返回空對象
//1、判斷是否允許自動裝配,如果否則直接返回空對象
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}如果是AutoConfigurationImportSelector類型,則需要判斷配置中的spring.boot.enableautoconfiguration是否為true,否則直接返回空對象
2.2)獲取@EnableAutoConfiguration注解上標注的類的元信息
//2、獲取@EnableAutoConfiguration注解上標注的類的元信息 AnnotationAttributes attributes = getAttributes(annotationMetadata);
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
+ " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}獲取注解上的元信息
2.3)獲取自動裝配的候選類名集合
//3、獲取自動裝配的候選類名集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
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;
}核心方法是SpringFactoriesLoader中的loadFactoryNames方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
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);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}搜索指定ClassLoader下所有的META/spring.factories資源內容,可能會返回多個
spring.factories中存放了所有自動裝配的候選類,這些類名格式都是XXXAutoConfiguration
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ ......
將這些資源內容作為Properties文件讀取,合并為一個Key為接口的全類名,Value是實現全類名列表的Map,作為loadSpringFactories的返回值
再從上一步返回的Map中查找并返回方法指定類名所映射的實現類全類名列表
2.4)去重
//4、可能存在重復,所以這里進行去重(因為是從配置文件類名獲取的,可能會配置重,所有先去下重) configurations = removeDuplicates(configurations);
因為配置文件可能存在重復,因此這里手動做了去重處理
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}使用到LinkedHashSet來去重
2.5)獲取自動裝配組件的排除名單
//5、獲取自動裝配組件的排除名單 Set<String> exclusions = getExclusions(annotationMetadata, attributes);
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
private List<String> getExcludeAutoConfigurationsProperty() {
if (getEnvironment() instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(getEnvironment());
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
.orElse(Collections.emptyList());
}
String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}排除名單來源于三個地方
@EnableAutoConfiguration注解中的exclue屬性@EnableAutoConfiguration注解中的excludeName屬性spring.autoconfigure.exclude配置
2.6)檢查排除名單是否合法
//6、檢查排除名單是否合法 checkExcludedClasses(configurations, exclusions);
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
for (String exclusion : exclusions) {
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
message));
}當排除類存在于當前ClassLoader,且不在自動裝配的候選類名單上,則當前排除類非法,會觸發(fā)IllegalStateException異常
2.7)排除exclusions中的類
//7、排除exclusions中的類 configurations.removeAll(exclusions);
2.8)執(zhí)行過濾操作
//8、執(zhí)行過濾操作,依賴前面AutoConfigurationMetadataLoader.loadMetadata獲取的autoConfigurationMetadata configurations = filter(configurations, autoConfigurationMetadata);
- 第一個參數:自動裝配候選名單
- 第二個參數:自動裝配的條件元信息
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
//將候選名單轉化為數組,目的是為了下面的參數匹配
String[] candidates = StringUtils.toStringArray(configurations);
//每個位置都標記是否需要過濾
boolean[] skip = new boolean[candidates.length];
//是否發(fā)生過過濾,目的是如果沒有發(fā)生過,則不需要遍歷skip數組進行篩選
boolean skipped = false;
//獲取當前beanClassLoader下的所有AutoConfigurationImportFilter
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
//匹配計算出結果
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
//根據結果更新skip
for (int i = 0; i < match.length; i++) {
//如果不匹配,則表示當前位置的類不滿足條件,需要過濾掉
if (!match[i]) {
skip[i] = true;
//這里是手動釋放引用,方便下次GC回收
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
//過濾出需要加載的類
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList<>(result);
}
//獲取當前beanClassLoader下的所有AutoConfigurationImportFilter
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}該方法核心就是根據spring-autoconfigure-metadata.properties中配置的條件進行候選名單的過濾,目的是減少不必要的類加載,提高啟動速度
2.9)觸發(fā)自動裝配的導入事件
//9、觸發(fā)自動裝配的導入事件 fireAutoConfigurationImportEvents(configurations, exclusions);
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}獲取當前beanClassLoader下的所有的AutoConfigurationImportListener實例,執(zhí)行對應的監(jiān)聽方法
2.10)返回自動裝配信息
//10、返回信息 return new AutoConfigurationEntry(configurations, exclusions);
返回的信息內容
List<String> configurations:最終需要自動裝配的類列表Set<String> exclusions:排除名單
四、總結(自動裝配流程)
- 查詢配置spring.boot.enableautoconfiguration,如果是true則繼續(xù),否則表示不啟用自動裝配,直接返回空對象
- 讀取所有META-INF/spring-autoconfigure-metadata.properties資源,保存為自動裝配的條件元信息,后續(xù)用來做最后的過濾
- 讀取所有META-INF/spring.factories資源中@EnableAutoConfiguration所關聯的自動裝配Class集合
- 讀取當前配置類所標注的@EnableAutoConfiguration屬性exclude和excludeName,以及spring.autoconfigure.exclude配置屬性合并為自動裝配Class排除集合
- 檢查自動裝配Class排除集合是否合法
- 排除候選自動裝配Class集合中的排除名單
- 使用之前加載的條件元信息,再次過濾候選自動裝配Class集合中Class不存在的成員
- 自動裝配Class集合過濾完成后,觸發(fā)AutoConfigurationImportEvent監(jiān)聽器執(zhí)行
- 返回裝配Class集合+排除名單
到此這篇關于關于SpringBoot的自動裝配原理詳解的文章就介紹到這了,更多相關SpringBoot的自動裝配內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
IDEA神器一鍵查看Java字節(jié)碼及其他類信息插件
這篇文章主要為大家介紹了一款IDEA神器,可以一鍵查看Java字節(jié)碼及其他類信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-01-01
Intellij IDEA菜單欄不見了(Main Menu as Separat
有人問博主,關于Intellij IDEA菜單欄找不到了,被不小心的操作給隱藏了,怎么辦?下面給大家分享解決方案,感興趣的朋友跟隨小編一起看看吧2024-06-06
springboot+vue制作后臺管理系統(tǒng)項目
本文詳細介紹了后臺管理使用springboot+vue制作,以分步驟、圖文的形式詳細講解,大家有需要的可以參考參考2021-08-08

