【Spring】@Import注解的使用和实现原理(十二)

时间:2021-04-17
本文章向大家介绍【Spring】@Import注解的使用和实现原理(十二) ,主要包括【Spring】@Import注解的使用和实现原理(十二) 使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、@Import概述

  @Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能。本文将介绍@Import注解的使用,并详细分析该注解的实现原理,同时会学习到Spring当中ImportSelector接口的和ImportBeanDefinitionRegistrar接口的使用和实现原理。

二、@Import介绍

  下面是Spring当中Import注解的源码,可以看出Import可以配合 Configuration, ImportSelector, ImportBeanDefinitionRegistrar 来使用,也可以用于一个普通类的导入。

 1 @Target(ElementType.TYPE)
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 public @interface Import {
 5 
 6     /**
 7      * {@link Configuration @Configuration}, {@link ImportSelector},
 8      * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
 9      */
10     Class<?>[] value();
11 
12 }
  • @Target表明了它能作用的范围,可以作用于类、接口、枚举类

  • 属性仅有一个value,表示的是一个类对象数组。例如value = {xx.class,yy.class},表示要将xx和yy交给Spring容器管理。

导入的类大体可以分成三大类

  1. 普通类

  2. 实现了ImportSelector接口的类

  3. 实现了ImportBeanDefinitionRegistrar接口的类

三、@Import介绍

1 普通类注入Spring容器的方式

  • 首先准备一个普通的实体类
     1 public class Person {
     2     private String name;
     3     private Integer age;
     4 
     5     public String getName() {
     6         return name;
     7     }
     8 
     9     public void setName(String name) {
    10         this.name = name;
    11     }
    12 
    13     public Integer getAge() {
    14         return age;
    15     }
    16 
    17     public void setAge(Integer age) {
    18         this.age = age;
    19     }
    20 
    21     @Override
    22     public String toString() {
    23         return "Person{" +
    24                 "name='" + name + '\'' +
    25                 ", age=" + age +
    26                 '}';
    27     }
    28 }
  • 使用基于java配置类的方式加载Spring的应用上下文。MainConfig如下
    1 @Configuration
    2 @Import(Person.class)
    3 public class MainConfig {
    4 }
  • main方法

    1 public class MainStarter {
    2     public static void main(String[] args) {
    3         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    4 
    5         Person person = (Person) context.getBean(Person.class);
    6         System.out.println(person);
    7     }
    8 }

  运行结果,可以发现Person普通类以及导入成功了

2 实现了ImportSelector接口的类注入Spring容器的方式

先来看看ImportSelector接口的定义,其中有两个方法

  • String[] selectImports(AnnotationMetadata importingClassMetadata)

  返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中

  • Predicate< String > getExclusionFilter()

  返回一个断言接口,该方法制定了一个对类全限定名的排除规则来过滤一些候选的导入类,默认不排除过滤。该接口可以不实现。

 1 public interface ImportSelector {
 2 
 3     // 返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中
 4     String[] selectImports(AnnotationMetadata importingClassMetadata);
 5 
 6     // 返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中
 7     @Nullable
 8     default Predicate<String> getExclusionFilter() {
 9         return null;
10     }
11 
12 }

  1、编写自己的ImportSelector接口的实现类

1 public class MyImportSelector implements ImportSelector {
2     @Override
3     public String[] selectImports(AnnotationMetadata importingClassMetadata) {
4         System.out.println("这里是选择导入类内部selectImports():选择要导入的类");
5         // Person类 需要导入
6         return new String[]{"com.test.importt.Person"};
7     }
8 }

  2、修改配置类,导入自定义 MyImportSelector类

1 @Configuration
2 @Import(MyImportSelector.class)
3 public class MainConfig {
4 }

  3、运行结果

    Person类被导入类,容器创建了person对象

  4、加上 getExclusionFilter() 方法实现,其中实现方法是将person排除

 1 public class MyImportSelector implements ImportSelector {
 2     @Override
 3     public String[] selectImports(AnnotationMetadata importingClassMetadata) {
 4         System.out.println("这里是选择导入类内部selectImports():选择要导入的类");
 5         // 返回Student 需要导入
 6         return new String[]{"com.test.importt.Person"};
 7     }
 8 
 9     @Override
10     public Predicate<String> getExclusionFilter() {
11         Predicate<String> predicate = new Predicate<String>() {
12             @Override
13             public boolean test(String s) {
14                 System.out.println(s);
15                 if (s.matches("com.test.importt.Person")) {
16                     return true;
17                 }
18                 return false;
19             }
20         };
21         return predicate;
22     }
23 }

  5、运行结果,容器中没有注入person

3 实现了ImportBeanDefinitionRegistrar接口的类注入Spring容器的方式

  该接口的目的是实现类的动态注入,同样的,先来看看ImportBeanDefinitionRegistrar接口的定义。

  一共定义了两个同名方法,都是用于将类的BeanDefinition注入。  

  唯一的区别就是,2个参数的方法,只能手动的输入beanName,而3个参数的方法,可以利用BeanNameGenerator根据beanDefinition自动生成beanName

 1 public interface ImportBeanDefinitionRegistrar {
 2 
 3     // 注册Bean定义
 4     default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
 5             BeanNameGenerator importBeanNameGenerator) {
 6 
 7         registerBeanDefinitions(importingClassMetadata, registry);
 8     }
 9 
10     // 注册Bean定义
11     default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
12     }
13 
14

   1、编写自己的ImportBeanDefinitionRegistrar接口的实现类

 1 public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
 2 
 3     @Override
 4     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 5         BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Person.class);
 6         AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
 7         registry.registerBeanDefinition("person", beanDefinition);
 8     }
 9 
10     //或者 使用如下的方法也可以,自动生成beanName
11     @Override
12     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
13         BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Person.class);
14         AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
15         String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);
16         registry.registerBeanDefinition(beanName, beanDefinition);
17     }
18 }

  2、修改配置类

1 @Configuration
2 @Import(MyImportBeanDefinitionRegistrar.class)
3 public class MainConfig {
4 }

  3、运行结果,同样是 Person 被导入了 容器

四、@Import实现原理

  @Import注解的三种使用方式相信读者已经掌握了。接下来就到了分析源码,看看Spring中是何时解析@Import注解,又是如何将3种类型的类注入到Spring容器当中的。

1、原理图

  

2、在ConfigurationClassParser解析配置类,解析的时候,处理 @Import 注解 会调用方法 processImports()

  processImports()源码如下:

 1 private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
 2                             Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
 3                             boolean checkForCircularImports) {
 4     // 导入
 5     if (importCandidates.isEmpty()) {
 6         return;
 7     }
 8     // 是否在导入栈的链路上,是的话报错
 9     if (checkForCircularImports && isChainedImportOnStack(configClass)) {
10         this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
11     } else {
12         // 放入导入栈中
13         this.importStack.push(configClass);
14         try {
15             // 获取Import导入进来的所有组件
16             for (SourceClass candidate : importCandidates) {
17                 // 判断该候选的是不是实现了ImportSelector的
18                 if (candidate.isAssignable(ImportSelector.class)) {
19                     // Candidate class is an ImportSelector -> delegate to it to determine imports
20                     Class<?> candidateClass = candidate.loadClass();
21                     // 实例化SelectImport组件
22                     ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
23                             this.environment, this.resourceLoader, this.registry);
24                     // 获取selector的排除过滤器
25                     Predicate<String> selectorFilter = selector.getExclusionFilter();
26                     if (selectorFilter != null) {
27                         // 或关系 合并 排除过滤器
28                         exclusionFilter = exclusionFilter.or(selectorFilter);
29                     }
30                     // 判断是不是延时的DeferredImportSelectors
31                     if (selector instanceof DeferredImportSelector) {
32                         // 是延迟导入类,则交给延迟处理器处理
33                         this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
34                     } else {
35                         //不是延时的
36                         // 调用selector的selectImports方法,获取导入的类名
37                         String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
38                         // 根据importClassNames 创建SourceClass集合
39                         Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
40                         // 递归处理导入configClass
41                         processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
42                     }
43                 }
44                 // 判断 导入的组件是不是ImportBeanDefinitionRegistrar
45                 else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
46                     // Candidate class is an ImportBeanDefinitionRegistrar ->
47                     // delegate to it to register additional bean definitions
48                     Class<?> candidateClass = candidate.loadClass();
49                     // 实例话 ImportBeanDefinitionRegistrar对象
50                     ImportBeanDefinitionRegistrar registrar =
51                             ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
52                                     this.environment, this.resourceLoader, this.registry);
53                     // 保存 ImportBeanDefinitionRegistrar对象
54                     configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
55                 } else {
56                     // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
57                     // process it as an @Configuration class
58                     // 就是一个普通的组件
59                     // 但是防止普通的组件是一个配置类 所以还是直接走了processConfigurationClass()方法
60                     // 在导入栈上注册导入
61                     this.importStack.registerImport(
62                             currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
63                     // 处理配置类
64                     processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
65                 }
66             }
67         } catch (BeanDefinitionStoreException ex) {
68             throw ex;
69         } catch (Throwable ex) {
70             throw new BeanDefinitionStoreException(
71                     "Failed to process import candidates for configuration class [" +
72                             configClass.getMetadata().getClassName() + "]", ex);
73         } finally {
74             // 从导入栈出栈
75             this.importStack.pop();
76         }
77     }
78 }

3、分别对@Import导入的是普通类、ImportSelector接口类 和 ImportBeanDefinitionRegistrar接口类做了相应的处理

4、解析之后,ConfigurationClassBeanDefinitionReader对象reader.loadBeanDefinitions() 加载成Bean定义

5、最后容器根据Bean定义,创建对应的类

参考:https://blog.csdn.net/gongsenlin341/article/details/113281596

原文地址:https://www.cnblogs.com/h--d/p/14670912.html