首頁 > 軟體

Spring註解@Import原理解析

2023-02-25 06:03:06

正文

在專案開發的過程中,我們會遇到很多名字為 @Enablexxx 的註解,比如@EnableApolloConfig@EnableFeignClients@EnableAsync 等。他們的功能都是通過這樣的註解實現一個開關,決定了是否開啟某個功能模組的所有元件的自動化設定,這極大的降低了我們的使用成本。

那麼你是好奇過 @Enablexxx 是如何達到這種效果呢,其作用機制是怎麼樣的呢?

@Import 原理

按照預設的習慣,我們會把某個功能模組的開啟註解定義為 @Enablexxx,功能的實現和名字格式其實無關,而是其內部實現,這裡用 @EnableAsync 來舉例子。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
 ……
}

可以看到除了3個通用註解,還有一個@Import(AsyncConfigurationSelector.class)註解,顯然它真正在這裡發揮了關鍵作用,它可以往容器中注入一個設定類。

在 Spring 容器啟動的過程中,執行到呼叫invokeBeanFactoryPostProcessors(beanFactory)方法的時候,會呼叫所有已經註冊的 BeanFactoryPostProcessor,然後會呼叫實現 BeanDefinitionRegistryPostProcessor 介面的後置處理器 ConfigurationClassPostProcessor ,呼叫其 postProcessBeanDefinitionRegistry() 方法, 在這裡會解析通過註解設定的類,然後呼叫 ConfigurationClassParser#doProcessConfigurationClass() 方法,最終會走到processImports()方法,對 @Import 註解進行處理,具體流程如下。

如果這部分流程不是很理解,推薦詳細閱讀一下 Spring 生命週期相關的程式碼,不過不重要,不影響理解後面的內容。

@Import 註解的功能是在ConfigurationClassParser類的 processImports()方法中實現的,對於這個方法我已經做了詳細的註釋,請檢視。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
   Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
   boolean checkForCircularImports) {

  // 如果使用@Import註解修飾的類集合為空,直接返回
  if (importCandidates.isEmpty()) {
   return;
  }
  // 通過一個棧結構解決迴圈引入
  if (checkForCircularImports && isChainedImportOnStack(configClass)) {
   this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  }
  else {
   // 新增到棧中,用於處理迴圈import的問題
   this.importStack.push(configClass);
   try {
    // 遍歷每一個@Import註解的類
    for (SourceClass candidate : importCandidates) {
     // 1. 
          // 檢驗設定類Import引入的類是否是ImportSelector子類
     if (candidate.isAssignable(ImportSelector.class)) {
      // Candidate class is an ImportSelector -> delegate to it to determine imports
      // 候選類是一個匯入選擇器->委託來確定是否進行匯入
      Class<?> candidateClass = candidate.loadClass();
      // 通過反射生成一個ImportSelect物件
      ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
        this.environment, this.resourceLoader, this.registry);
      // 獲取選擇器的額外過濾器
      Predicate<String> selectorFilter = selector.getExclusionFilter();
      if (selectorFilter != null) {
       exclusionFilter = exclusionFilter.or(selectorFilter);
      }
            
      // 判斷參照選擇器是否是DeferredImportSelector介面的範例
      // 如果是則應用選擇器將會在所有的設定類都載入完畢後載入
      if (selector instanceof DeferredImportSelector) {
       // 將選擇器新增到deferredImportSelectorHandler範例中,預留到所有的設定類載入完成後統一處理自動化設定類
       this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
      }
            
      else {
       // 獲取引入的類,然後使用遞迴方式將這些類中同樣新增了@Import註解除參照的類
              // 執行 ImportSelector.selectImports
       String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
       Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
       // 遞迴處理,被Import進來的類也有可能@Import註解
       processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
      }
     }
          // 2.
     // 如果是實現了ImportBeanDefinitionRegistrar介面的bd
     else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
      // Candidate class is an ImportBeanDefinitionRegistrar ->
      // delegate to it to register additional bean definitions
      // 候選類是ImportBeanDefinitionRegistrar  -> 委託給當前註冊器註冊其他bean
       Class<?> candidateClass = candidate.loadClass();
      ImportBeanDefinitionRegistrar registrar =
        ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
          this.environment, this.resourceLoader, this.registry);
      /**
       * 放到當前configClass的importBeanDefinitionRegistrars中
       * 在ConfigurationClassPostProcessor處理configClass時會隨之一起處理
       */
      configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
     }
     else {
      // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
      // process it as an @Configuration class
      // 候選類既不是ImportSelector也不是ImportBeanDefinitionRegistrar-->將其作為@Configuration設定類處理
      this.importStack.registerImport(
        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
      /**
       * 如果Import的型別是普通類,則將其當作帶有@Configuration的類一樣處理
       */
      processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
     }
    }
   }
   catch (BeanDefinitionStoreException ex) {
   ……
   finally {
    this.importStack.pop();
   }
  }
 }

上述程式碼的核心邏輯無非就是如下幾個步驟。

  • 找到被 @Import 修飾的候選類集合,依次迴圈遍歷。
  • 如果該類實現了ImportSelector介面,就呼叫 ImportSelectorselectImports() 方法,這個方法返回的是一批設定類的全限定名,然後遞迴呼叫processImports()繼續解析這些設定類,比如可以 @Import 的類裡面有 @Import 註解,在這裡可以遞迴處理。
  • 如果被修飾的類沒有實現 ImportSelector 介面,而是實現了ImportBeanDefinitionRegistrar 介面,則把對應的範例放入importBeanDefinitionRegistrars 這個Map中,等到ConfigurationClassPostProcessor處理 configClass 的時候,會與其他設定類一同被呼叫 ImportBeanDefinitionRegistrarregisterBeanDefinitions() 方法,以實現往 Spring 容器中注入一些 BeanDefinition。
  • 如果以上的兩個介面都未實現,則進入 else 邏輯,將其作為普通的 @Configuration 設定類進行解析。

所以到這裡,你應該明白 @Import 的作用機制了吧。對上述邏輯我總結了一張圖,如下。

範例 @EnableAsync

繼續之前提到的 @EnableAsync 作為例子,原始碼如下。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
 Class<? extends Annotation> annotation() default Annotation.class;
 boolean proxyTargetClass() default false;
 AdviceMode mode() default AdviceMode.PROXY;
 int order() default Ordered.LOWEST_PRECEDENCE;
}
// 
@Override
 public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
  ……
    // 獲取 Mode
  AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
    // 模板方法,由子類去實現
  String[] imports = selectImports(adviceMode);
  if (imports == null) {
   throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
  }
  return imports;
 }

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

 @Override
 @Nullable
 public String[] selectImports(AdviceMode adviceMode) {
  switch (adviceMode) {
   case PROXY:
    return new String[] {ProxyAsyncConfiguration.class.getName()};
   case ASPECTJ:
    return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
   default:
    return null;
  }
 }

}

它通過 @Import 註解引入了AsyncConfigurationSelector設定類,它繼承了 AdviceModeImportSelector 類,而後者實現了 ImportSelector 介面,裡面的實現了一個由註解指定 mode 屬性來決定返回的設定類的邏輯,而 mode 的預設值就是 AdviceMode.PROXY

對應 switch 邏輯,將返回 ProxyAsyncConfiguration類的全限定名。這就對應了 @Import 處理邏輯的第一個 if 邏輯塊,它將會解析這個類,然後遞迴呼叫processImports(),再次進入此方法,進入第三個else邏輯塊,將其當作一個普通設定類解析。可以看到 ProxyAsyncConfiguration 其實就是 @Configuration 類,它的作用是註冊一個 Bean 物件 AsyncAnnotationBeanPostProcessor。

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
   @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
      ……
      return bpp;
   }
}

以上就是Spring註解@Import原理解析的詳細內容,更多關於Spring註解@Import原理的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com