Spring Boot 中 @Enablexx注解原理

在项目开发的过程中,我们会遇到很多名字为 @Enablexxx 的注解,比如@EnableApollo-Config、 @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 配置类进行解析
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/1d841b33efc74a209e784b27eb1f4954.png)
示例 @EnableAsync
继续之前提到的 @EnableAsync 作为例子,源码如下。

```java
@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 逻辑的第一个 case,将返回 ProxyAsyncConfiguration类的全限定名。这就对应了 @Import 处理逻辑的第一个 if 逻辑块,它将会解析这个类,然后递归调用processImports(),再次进入此方法,进入第三个else逻辑块,将其当作一个普通配置类解析。可以看到 ProxyAsync-Configuration 其实就是 @Configuration 类,它的作用是注册一个 Bean 对象 AsyncAnnotation-BeanPostProcessor@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使用及原理详解
@Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能,向Spring容器中注入bean,也对应实现了与Spring XML中的元素相同的功能,注解定义如下:
less复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

/**

  • {@link Configuration @Configuration}, {@link ImportSelector},
  • {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
    */
    Class<?>[] value();

    }

从定义上来看,@Import注解非常简单,只有一个属性value(),类型为类对象数组,如value={A.class, B.class},这样就可以把类A和B交给Spring容器管理。但是这个类对象需要细分为三种对象,也对应着@Import的三种用法如下:

普通类
实现了ImportSelector接口的类(这是重点~Spring Boot的自动配置原理就用到这种方式)
实现了ImportBeanDefinitionRegistrar接口的类
2.@Import的三种用法
2.1 注入普通类
这种方式很简单,直接上代码,首先先随便定义一个普通类:这里我定义了一个Student类
kotlin复制代码@Data
public class Student {
private Long id;
private String name;
private Integer age;
}

接下来就是声明一个配置类,然后使用@Import导入注入即可:
typescript复制代码

@Configuration
@Import({Student.class})
public class MyConfig {@Bean
    public Person person01() {
        Person person = Person.builder().id(1l).name("shepherd").age(25).build();
        return person;
    }
​
​
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        // 遍历Spring容器中的beanName
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }}

这里我用@Bean注入了一个bean,想证实一下@Import实现的功能与其类似,执行上面的main()结果如下:
kotlin复制代码org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfig
com.shepherd.common.config.Student
person01

2.2 实现了ImportSelector接口的类
这一方式比较重要,也可以说是@Import最常用的方式,Spring Boot的自动装配原理就用到了这种方式,所以得认真学习一下。

我们先来看看ImportSelector这个接口的定义
selectImports( )返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中。注意如果为null,要返回空数组,不然后续处理会报错空指针:
getExclusionFilter()该方法制定了一个对类全限定名的排除规则来过滤一些候选的导入类,默认不排除过滤。该接口可以不实现

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.shepherd.common.config.Student",
        "com.shepherd.common.config.Person"};
    }
}

最后在配置类中使用@Import导入:
less复制代码@Configuration

@Import({MyImportSelector.class})
public class MyConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        // 遍历Spring容器中的beanName
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfig
com.shepherd.common.config.Student
com.shepherd.common.config.Person

可以看出,Spring没有把MyImportSelector当初一个普通类进行处理,而是根据selectImports( )返回的全限定类名数组批量注入到Spring容器中。当然你也可以实现重写getExclusionFilter()方法排除某些类,比如你不想注入Person类,你就可以通过这种方式操作一下即可,这里就不再展示代码案例,可以自行尝试。
2.3 实现了ImportBeanDefinitionRegistrar接口的类
这种方式通过描述就可以知道是通过实现ImportBeanDefinitionRegistrar将要注入的类添加到BeanDefinition注册中心,这样Spring 后续会根据bean定义信息将类注入到容器中。
老规矩,我们先看看ImportBeanDefinitionRegistrar的定义:
可以看到一共有两个同名重载方法,都是用于将类的BeanDefinition注入。
唯一的区别就是,2个参数的方法,只能手动的输入beanName,而3个参数的方法,可以利用BeanNameGenerator根据beanDefinition自动生成beanName
自定义一个类实现ImportBeanDefinitionRegistrar:
java复制代码

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    //使用 BeanNameGenerator自动生成beanName
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Person.class);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
    // 手动指定beanName
//    @Override
//    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Student.class);
//        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
//        registry.registerBeanDefinition("student001", beanDefinition);
//    }
}

3.@Import的实现原理
探究@Import注解实现源码之前,不得不引出Spring中一个非常重要的类:ConfigurationClassPostProcessor,从名字可以看出它是一个BeanFactoryPostProcessor,主要用于处理一些配置信息和注解扫描。之前我们就在总结的 @Configuration 和 @Component区别于实现原理文章中讲述过该类的核心所在,感兴趣的可根据该文章链接跳转自行查看,不出意外地,@Import注解的扫描、解析也在其中,其流程图如下所示:
在这里插入图片描述
ConfigurationClassPostProcessor既然是一个后置处理器,我们就直接从其后置处理方法入手即可,经过debug调试发现ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()是完成对@Component,@ComponentScan,@Bean,@Configuration,@Import等等注解的处理的入口方
实现了ImportSelector接口的类,调用getExclusionFilter()方法,如果不为空,那么就进行过滤,过滤后调用selectImports()方法,得到要注入的类的全限定名。根据类全限定名,得到类元信息。然后递归的调用processImports()方法
实现了ImportBeanDefinitionRegistrar接口的类,会实例化这个类,放入集合importBeanDefinitionRegistrars当中。
普通类型的类(上面两个都不满足),那么就把它当作是配置类来处理,调用processConfigurationClass()方法,最终会放入到configurationClasses这个集合当中
经过一系列的递归调用,实现了ImportBeanDefinitionRegistrar接口的类,会放入到importBeanDefinitionRegistrars集合当中,其余的类都放入到configurationClasses集合当中。 之后就会回到processConfigBeanDefinitions方法,也就是执行完了ConfigurationClassParser的parse()方法。此时会执行loadBeanDefinitions将configurationClasses集合当中类加载的Spring容器当中,并且从 importBeanDefinitionRegistrars缓存当中拿到所有的ImportBeanDefinitionRegistrar并执行registerBeanDefinitions方法。

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值