SpringBoot 自動裝配原理,一文掌握!

本文詳細講解了 SpringBoot 自動裝配原理,可以直接拉到最後看總結。由於 Spring 源碼比較複雜,是需要一些基礎的。

Spring Boot 一個很大的特點就是極大的簡化了原來在 Spring 中複雜的 XML 文件配置過程,讓我們對 Spring 應用對搭建和開發變得極其簡單。既然可以簡化配置,那就意味着很多配置都是需要默認的,這也使其提出了約定大於配置和自動裝配的思想。一些通用的配置會默認設置好,整個組件需要的時候直接載入,不需要的時候可以整個卸載。

通過 Spring Boot 我們可以很方便的引入新的組件,只需要在依賴文件中加入對應的 xxx-starter 即可,然後把一些必要的配置比如 url 信息做個簡單的設置,或者增加一個 @EnableXXX,就可以開始使用了。

這裏以 Feign 爲例:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableFeignClients
public class SampleApplication {

   public static void main(String[] args) {
      SpringApplication.run(SampleApplication.class, args);
   }
}

只需要這樣,就可以將 Feign 引入項目中了,接下來根據自己的需要定義對應的 Feign client 即可,是不是非常簡單。

Spring Boot 到底是如何做到的呢?

自動裝配原理

自動裝配的入口

自動裝配的基礎,是 Spring 從 4.x 版本開始支持 JavaConfig,讓開發者可以免去繁瑣的 xml 配置形式,而是使用熟悉的 Java 代碼加註解,通過 @Configuration、@Bean 等註解可以直接向 Spring 容器注入 Bean 信息。

那麼就有種設想,如果我把一些必須的 Bean 以 Java 代碼方式準備好呢,只需要引入對應的配置類,相應的 Bean 就會被加載到 Spring 容器中。所以,有了這個基礎 Spring Boot 就有了實現自動裝配的可能。

還是以 Feign 爲例,FeignAutoConfiguration 這個類就是一個 Feign 的自動裝配類,我們來探究一下他是如何生效的。

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
      FeignHttpClientProperties.class })
public class FeignAutoConfiguration {

   @Autowired(required = false)
   // 注入了一堆FeignClientSpecification
   private List<FeignClientSpecification> configurations = new ArrayList<>();

   @Bean
   public HasFeatures feignFeature() {
      return HasFeatures.namedFeature("Feign", Feign.class);
   }

   @Bean
   public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      context.setConfigurations(this.configurations);
      return context;
   }
}

在每個 Spring Boot 的啓動類上,都會有這樣一個複合註解 @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) })
public @interface SpringBootApplication {
}

……省略其他註解
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

上面四個都是一些通用註解,關鍵在於下面的 @EnableAutoConfiguration註解,從名字就可以看出來,它可以開啓自動配置。在之前一篇文章我們已經講過,通過 @Import 註解我們可以導入一些自定義的 BeanDefination 信息,或者導入一些配置類。在 EnableAutoConfiguration 內部,它使用 @Import 標註了 AutoConfigurationImportSelector。

可以看出,它實現了 ImportSelector 接口,之前我們已經在講結果 @Import 註解是如何生效的。

下面,先對後面涉及的一些類和文件做一個簡單介紹。

ImportSelector

看名字就可以知道,這是用於導入的選擇器。String[] selectImports() 方法會返回需要導入的配置類的全路徑名。在 Spring 容器啓動的過程中會調用 invokeBeanFactoryPostProcessors,然後會執行一個重要的後置處理器 ConfigurationClassPostProcessor ,完成配置類的解析,這裏會處理 ImportSelector 返回的這些類,將其加載到容器中。

public interface ImportSelector {
   String[] selectImports(AnnotationMetadata importingClassMetadata);
}

DeferredImportSelector

DeferredImportSelector 繼承了 ImportSelector,它的作用是用於延遲導入。在所有的需要處理的配置類解析過程中,繼承此接口的解析排在最後,並且在有多個 DeferredImportSelector 實現類的情況下,可以繼承 Ordered 實現排序的效果。

public interface DeferredImportSelector extends ImportSelector {

   @Nullable
   default Class<? extends Group> getImportGroup() {
      return null;
   }

   interface Group {
   }
}

繼續之前的內容,AutoConfigurationImportSelector 實現了 DeferredImportSelector 接口,所

在執行 ConfigurationClassParser.processImports()方法的時候,最終會調用到下面這段邏輯。一般繼承 ImportSelector 會執行其 selectImport 方法。但是這裏不同的是,它還繼承了 DeferredImportSelector 接口,對 ImportSelector 只是間接繼承。在 processImports() 方法中有這樣的額外判斷,如果是 DeferredImportSelector 的子類,將會執行 deferredImportSelectorHandler.handle(),最終會回調 AutoConfigurationImportSelectorprocess 方法。具體的調用過程請見下圖。

Spring Boot 2.x 的版本與 1.x 有所不同,1.x 是回調 selectImports 方法。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
……
 if (candidate.isAssignable(ImportSelector.class)) {
 ……
    // 執行這段邏輯
 if (selector instanceof DeferredImportSelector) {
  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
 }
 else {
 ……
  processImports(configClass, currentSourceClass, importSourceClasses, false);
 }
 ……
}

讀取配置

process() 方法中主要做了一件事,讀取並解析 spring.factories 配置文件中的信息,將這些配置文件對應的全路徑類名都放入 AutoConfigurationEntry 集合中。接下來詳細解釋相關邏輯。

getAutoConfigurationMetadata() 方法讀取並解析了 spring-autoconfigure-metadata.properties 文件,用於控制自動裝配條件。關於這個路徑信息,追蹤方法可以找到,比較簡單。

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

getAutoConfigurationEntry() 方法會獲取需要自動裝配的類和需要排除的類,讀取的文件是 META-INF/spring.factories

關於這個文件路徑是怎麼指定的,可以在下面方法中看到。一直深入追蹤,在 loadSpringFactories 方法中,會加載 META-INF/spring.factories 路徑下的配置內容,並且這個路徑是硬編碼寫死的。在全部讀取完畢之後,會放在一個 Map 中,key 爲類名,value 爲對應的自定義配置類。getSpringFactoriesLoaderFactoryClass()  方法會固定返回 EnableAutoConfiguration.class,所以這裏只會返回 EnableAutoConfiguration 對應的配置內容,配置文件內容如下圖。

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   ……
   // getAutoConfigurationMetadata() 方法讀取並解析了 spring-autoconfigure-metadata.properties 文件,用於控制自動裝配條件
   // AutoConfigurationEntry 方法會獲取需要自動裝配的類和需要排除的類
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
   // 添加到 AutoConfigurationEntry集合中等待加載
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

// 返回自動配置的類名,加載Spring.factories中的配置信息
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // loadFactoryNames 會讀取對應的配置文件,位置在META-INF/spring.factories中
  // getSpringFactoriesLoaderFactoryClass 返回 EnableAutoConfiguration.class
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
  ……
   return configurations;
}

具體執行方法的調用鏈路如下:

加載配置

到這裏我們就明白,spring.properties 文件中的配置類是如何加載的,但是問題來了,他什麼時候註冊到 Spring 容器中呢?

回到之前執行過程中的processGroupImports 方法(在前面的圖片已用紅框標註了出來),這裏會調用 getImports 拿到配置類信息,然後再次調用類信息,然後遞歸調用 processImports,這個方法之前的文章已經解釋過了,如果是配置類會解析並註冊 Spring 的 Bean 信息,具體請自行查看之前文章。

public void processGroupImports() {
   for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
     // getImports 得到解析後的類名
      grouping.getImports().forEach(entry -> {
         ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
         try {
           // 再次調用 processImports 遞歸處理,對配置類解析,註冊爲 Bean
            processImports(configurationClass, asSourceClass(configurationClass),
                  asSourceClasses(entry.getImportClassName())false);
         }
         catch (BeanDefinitionStoreException ex) {
           ……
         }
      });
   }
}

另外,再額外說一下 getImports 方法。之前 process 方法並沒有返回值,而是把配置信息都保存在了 autoConfigurationEntries 中,所以在執行完 process 之後會緊接着執行 selectImports()。它的功能主要是排除需要排除的類信息,並且在這裏按照 spring-autoconfigure-metadata.properties 中指定的順序排序,然後再返回類信息。

public Iterable<Group.Entry> getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
   // 調用 process 邏輯
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
   return this.group.selectImports();
}

public Iterable<Entry> selectImports() {
 if (this.autoConfigurationEntries.isEmpty()) {
  return Collections.emptyList();
 }
 // 獲取所有需要排除的類集合
 Set<String> allExclusions = this.autoConfigurationEntries.stream()
   .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
 // 獲取所有需要裝配的類集合
 Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
   .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
   .collect(Collectors.toCollection(LinkedHashSet::new));
 // 移除所有排除類
 processedConfigurations.removeAll(allExclusions);
 // 將需要加載的類排序返回,排序規則按照 spring-autoconfigure-metadata.properties 中指定的順序
 return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
   .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
   .collect(Collectors.toList());
}

總結

最後,我們再次總結一下整個自動裝配的過程。

  1. 引入 META-INF/spring.factories 配置文件,在 EnableAutoConfiguration 對應的 value 中配置需要引入的配置類。

  2. 啓動類增加 @EnableAutoConfiguration 註解,@SpringBootApplication 已經自帶。

  3. @EnableAutoConfiguration 註解中通過 @Import 標註了 AutoConfigurationImportSelector 類。

  4. AutoConfigurationImportSelector 繼承了 DeferredImportSelector 接口,在 Spring 生命週期處理 BeanFactoryPostProcessors 的時候會對配置信息進行後置處理,這是會調用到 AutoConfigurationImportSelector.process 方法。

  5. process 方法中會讀取 META-INF/spring.factories 配置文件中的內容爲 Key-Value 形式,讀取完後值返回 key = EnableAutoConfiguration 對應的配置類信息,保存到 autoConfigurationEntries 中。

  6. AutoConfigurationGroup.selectImports 方法返回排序、篩選後的配置類信息,然後依次遍歷,遞歸調用 processImports, 根據這些配置類的全路徑名讀取並註冊在 Spring 容器中。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4WKp5QcDHxr-qvNaeg88yQ