当前位置:主页 > java教程 > Spring注解@Import原理

Spring注解@Import原理解析

发布:2023-03-25 16:15:02 59


我们帮大家精选了相关的编程文章,网友关永怡根据主题投稿了本篇教程内容,涉及到Spring注解@Import、Spring、@Import、Spring注解、Spring注解@Import原理相关内容,已被485网友关注,涉猎到的知识点内容可以在下方电子书获得。

Spring注解@Import原理

正文

在项目开发的过程中,我们会遇到很多名字为 @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原理的资料请关注码农之家其它相关文章!


参考资料

相关文章

  • SpringBoot Validation提示信息国际化配置方式

    发布:2023-03-25

    这篇文章主要介绍了SpringBoot Validation提示信息国际化配置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教


  • Spring事务管理的使用细则浅析

    发布:2023-03-14

    事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就 回退到事务开始未进行操作的状态。事务管理是Spring框架中最为常用的功能之一,我们在使用Spring开发应用时,大部分情况下也都需要使用事务


  • SpringBoot访问静态资源的配置及顺序说明

    SpringBoot访问静态资源的配置及顺序说明

    发布:2022-07-06

    给网友们整理关于SpringBoot的教程,这篇文章主要介绍了SpringBoot访问静态资源的配置及顺序说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教


  • springboot集成@DS注解实现数据源切换的方法示例

    发布:2022-09-14

    给大家整理一篇关于springboot的教程,本文主要介绍了springboot集成@DS注解实现数据源切换的方法示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下


  • SpringCloud Hystrix-Dashboard仪表盘的实例详解

    发布:2020-02-19

    这篇文章主要介绍了SpringCloud Hystrix-Dashboard仪表盘的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


  • SpringBoot+Lucene实例介绍

    发布:2020-07-16

    这篇文章主要介绍了详解SpringBoot+Lucene案例介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


  • Spring底层原理由浅入深探究

    发布:2023-03-24

    Spring事务有可能会提交,回滚、挂起、恢复,所以Spring事务提供了一种机制,可以让程序员来监听当前Spring事务所处于的状态,这篇文章主要介绍了Spring底层事务原理,需要的朋友可以参考下


  • SpringBoot实现设置全局和局部时间格式化

    发布:2023-03-03

    本文主要介绍了SpringBoot实现设置全局和局部时间格式化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


网友讨论