SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現

SpringBoot系列文章簡介

SpringBoot源碼閱讀輔助篇:

  Spring IoC容器與應用上下文的設計與實現

SpringBoot啟動流程源碼分析:

  1. SpringBoot啟動流程分析(一):SpringApplication類初始化過程
  2. SpringBoot啟動流程分析(二):SpringApplication的run方法
  3. SpringBoot啟動流程分析(三):SpringApplication的run方法之prepareContext()方法
  4. SpringBoot啟動流程分析(四):IoC容器的初始化過程
  5. SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現
  6. SpringBoot啟動流程分析(六):IoC容器依賴注入

筆者註釋版Spring Framework與SpringBoot源碼git傳送門:請不要吝嗇小星星

  1. spring-framework-5.0.8.RELEASE
  2. SpringBoot-2.0.4.RELEASE

自定義Starter:

  SpringBoot應用篇(一):自定義starter

一、前言

  上一篇文章,通過分析refresh()方法中的invokeBeanFactoryPostProcessors()方法,分析了IoC容器的初始化過程,這一節從代碼上如下所示,接上一節ConfigurationClassParser類中的parse()方法,接着分析SpringBoot的自動裝配原理。

 1 // ConfigurationClassParser類
 2 public void parse(Set<BeanDefinitionHolder> configCandidates) {
 3     this.deferredImportSelectors = new LinkedList<>();
 4     for (BeanDefinitionHolder holder : configCandidates) {
 5         BeanDefinition bd = holder.getBeanDefinition();
 6         try {
 7             // 如果是SpringBoot項目進來的,bd其實就是前面主類封裝成的 AnnotatedGenericBeanDefinition(AnnotatedBeanDefinition接口的實現類)
 8             if (bd instanceof AnnotatedBeanDefinition) {
 9                 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
10             } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
11                 parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
12             } else {
13                 parse(bd.getBeanClassName(), holder.getBeanName());
14             }
15         } catch (BeanDefinitionStoreException ex) {
16             throw ex;
17         } catch (Throwable ex) {
18             throw new BeanDefinitionStoreException(
19                     "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
20         }
21     }
22     // 加載默認的配置---》(對springboot項目來說這裏就是自動裝配的入口了)
23     processDeferredImportSelectors();
24 }

 

二、SpringBoot自動裝配原理。

2.1、@SpringBootApplication註解

  對這個註解詳細大家一定非常熟悉了。再來好好看看這個註解。

 1 @Target(ElementType.TYPE)
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Inherited
 5 @SpringBootConfiguration
 6 @EnableAutoConfiguration
 7 @ComponentScan(excludeFilters = {
 8         @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
 9         @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
10 public @interface SpringBootApplication {
11     ...
12 }

  接着看@EnableAutoConfiguration

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @AutoConfigurationPackage
6 @Import(AutoConfigurationImportSelector.class)
7 public @interface EnableAutoConfiguration {
8     ...
9 }

  OK,看到@Import(AutoConfigurationImportSelector.class)導入了一個重要的類AutoConfigurationImportSelector。

2.2、AutoConfigurationImportSelector

 1 // AutoConfigurationImportSelector類
 2 //自動裝配
 3 @Override
 4 public String[] selectImports(AnnotationMetadata annotationMetadata) {
 5     if (!isEnabled(annotationMetadata)) {
 6         return NO_IMPORTS;
 7     }
 8     AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
 9             .loadMetadata(this.beanClassLoader);
10     AnnotationAttributes attributes = getAttributes(annotationMetadata);
11     //獲取所有的自動配置類(META-INF/spring.factories中配置的key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的類)
12     List<String> configurations = getCandidateConfigurations(annotationMetadata,
13             attributes);
14     configurations = removeDuplicates(configurations);
15     //需要排除的自動裝配類(springboot的主類上 @SpringBootApplication(exclude = {com.demo.starter.config.DemoConfig.class})指定的排除的自動裝配類)
16     Set<String> exclusions = getExclusions(annotationMetadata, attributes);
17     checkExcludedClasses(configurations, exclusions);
18     //將需要排除的類從 configurations remove掉
19     configurations.removeAll(exclusions);
20     configurations = filter(configurations, autoConfigurationMetadata);
21     fireAutoConfigurationImportEvents(configurations, exclusions);
22     return StringUtils.toStringArray(configurations);
23 }

  至於怎麼從章節一中提到的ConfigurationClassParser類中的parse()===>processDeferredImportSelectors()==>AutoConfigurationImportSelector#selectImports(),篇幅有限不做過多介紹。

   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 

  我們來看一下getCandidateConfigurations()方法是怎麼拿到這些自動配置類的。

// AutoConfigurationImportSelector類
1
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 2 AnnotationAttributes attributes) { 3 List<String> configurations = SpringFactoriesLoader.loadFactoryNames( 4 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); 5 Assert.notEmpty(configurations, 6 "No auto configuration classes found in META-INF/spring.factories. If you " 7 + "are using a custom packaging, make sure that file is correct."); 8 return configurations; 9 }

  是不是又看到一個十分熟悉的方法loadFactoryNames(),沒錯,其實我們在分析SpringBoot啟動流程的第一篇文章的時候,就已經分析了,SpringBoot是如何從META-INF/spring.factories中加載指定key的value的。ok,我們在這裏再次回顧一遍。

  看看loadFactoryNames()方法

// SpringFactoriesLoader類
1
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { 2 String factoryClassName = factoryClass.getName(); 3 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); 4 }

  debug,看看要從META-INF/spring.factories中加載的類的key,如下圖所示:org.springframework.boot.autoconfigure.EnableAutoConfiguration

  回到selectImports()方法,debug,跳過List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);看一下configurations

  竟然有110個,那這些類都在哪裡呢?看spring-boot-autoconfigure(當然在SpringBoot的工程中,也不止這一個依賴包中存在該配置文件)工程下的META-INF/spring.factories,我們能看到org.springframework.boot.autoconfigure.EnableAutoConfiguration定義了一大堆。

  其中還有一個com.demo.starter.config.DemoConfig是我自定義的starter。如下所示,我在測試工程中添加了自定義starter的依賴,所以SpringBoot就能掃描到。

1 <dependency>
2     <groupId>com.demo</groupId>
3     <artifactId>demo-spring-boot-starter</artifactId>
4     <version>0.0.1-RELEASE</version>
5 </dependency>

   繼續看Set<String> exclusions = getExclusions(annotationMetadata, attributes);方法,該方法是排除主類上@SpringBootApplication註解上排除的自動裝配的類。比如我們在該註解上排除我們自定義starter的自動裝配的類,@SpringBootApplication(exclude = {com.demo.starter.config.DemoConfig.class})(當然也可以用excludeName進行排除),那麼在後面的configurations.removeAll(exclusions);方法中將會刪除我們的com.demo.starter.config.DemoConfig.class。

  configurations = filter(configurations, autoConfigurationMetadata);該行代碼將會過濾掉不需要裝配的類。過濾的邏輯有很多,比如我們常用的@ConditionXXX註解。如下所示:

 1 @ConditionalOnBean:容器中有指定的Bean 
 2 @ConditionalOnClass:當類路徑下有指定的類
 3 @ConditionalOnExpression:基於SpEL表達式作為判斷條件  
 4 @ConditionalOnJava:基於JVM版本作為判斷條件  
 5 @ConditionalOnJndi:在JNDI存在的條件下查找指定的位置  
 6 @ConditionalOnMissingBean:當容器中沒有指定Bean的情況下  
 7 @ConditionalOnMissingClass:當類路徑下沒有指定的類
 8 @ConditionalOnNotWebApplication:當前項目不是Web項目
 9 @ConditionalOnProperty:配置文件中指定的屬性是否有指定的值  
10 @ConditionalOnResource:類路徑下是否有指定的資源  
11 @ConditionalOnSingleCandidate:當指定Bean在容器中只有一個,或者雖然有多個但是指定首選Bean
12 @ConditionalOnWebApplication:當前項目是Web項目的條件下

   至於如何將這些類解析成BeanDefinition並註冊進beanDefinition中的,和上一節講的過程是一樣的,不再贅述了。

  debug,跳過refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory);方法。如下圖所示,最終在beanFactory的BeanDefinitionMap中找到了自定義starter中的自動裝配的類。

 

  綜合本文和上一篇博文我們詳細的梳理了IoC容器的初始化過程,到此IoC容器的初始化過程就結束了。

 

  

  原創不易,轉載請註明出處。

  如有錯誤的地方還請留言指正。

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!