Import注解源码

Image by Uladzislau Sinitsa from Pixabay

前言

平常在使用SpringBoot的过程中,经常会使用到@EnableXXX的注解,而随之一起的还有一个@Import注解,这次就专门来看@Import的源码

正文

先摘抄一部分它的英文注释吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * // 导入一个或多个组件类,代表性的就是Configuration类型的类
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * // 提供与spring的xml中的import标签相同的功能。可以导入配置类,普通类,ImportSelector或ImportBeanDefinitionRegistrar接口的实现类
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 * <p>May be declared at the class level or as a meta-annotation.
 *
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 *
 */

大概意思就是:

  1. 可以导入一个类或多个类至容器中,和xml中的import标签功能一样
  2. 可导入普通类,Configuration类,ImportSelector或ImportBeanDefinitionRegistrar接口的实现类
  3. 导入配置文件,该配置文件可被其他的bean注入,也可以在配置中注入其他的bean。

例子

  1. 新建一个springboot项目
  2. 在启动类上层建立同层级的包。例如,启动类test.javaa.b.c.d下面,那么在c包下建立e包,ed处于同一级目录。

为什么要有第二点,因为springboot会扫描启动类同级目录下的文件,如果在同一级目录,直接使用@Componment就行了。所以个人觉得@Import就是导入与项目不是同一级目录的类。

导入普通类

在e包下新建两个类,TestImportA和TestImportB:

1
2
3
4
5
6
7
8
9
10
11
12
13

    public class TestImportA {
        public void test(){
            System.out.println("TestImportB run");
        }
    }

    public class TestImportB {
        public void test(){
            System.out.println("TestImportB run");
        }
    }

然后在启动类上面添加:

1
2
3
4
5
6
7
8
9
10
11

    @SpringBootApplication
    @Import({TestImportA.class, TestImportB.class})
    public class SpringbootOneApplication {
        public static void main(String[] args) {
            ConfigurableApplicationContext run = SpringApplication.run(SpringbootOneApplication.class, args);
            TestImportB testImportB = run.getBean(TestImportB.class);
            testImportB.test();
        }
    }

这样,不会被扫描到的TestImportA.class, TestImportB.class就可以通过@Import注解扫描到,并放进容器了。

导入配置类

在e包下新建配置类TestImportConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14

    @Configuration
    public class TestImportConfig {
        @Bean
        public TestImportA testImportA() {
            return new TestImportA();
        }
    
        @Bean
        public TestImportB testImportB() {
            return new TestImportB();
        }
    }

然后在启动类上面添加:

1
2
3
4
5
6
7
8
9
10
11

    @SpringBootApplication
    @Import(TestImportConfig.class)
    public class SpringbootOneApplication {
        public static void main(String[] args) {
            ConfigurableApplicationContext run = SpringApplication.run(SpringbootOneApplication.class, args);
            TestImportB testImportB = run.getBean(TestImportB.class);
            testImportB.test();
        }
    }

效果和第一种是一样的。

导入ImportSelector的实现类

  1. 新建一个注解类TestImport
1
2
3
4
5
6
7
8
9
10

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportSelectorImpl.class) // 注意建立ImportSelectorImpl类后加上这部分注解
public @interface TestImport {
    String value() default "value";
    boolean flag() default false;
}

说一下为什么要新建一个注解类。 ImportSelector的接口String[] selectImports(AnnotationMetadata importingClassMetadata),该接口接收了一个AnnotationMetadata, 这个类里面就包含了@Import注解所在的地方(类,接口,枚举)的注解信息,这样我们就可以在启动配置的时候获取一些需要的信息了。最典型的就是@EnableXXX注解, 在@EnableXXX中使用@Import导入ImportSelector实现类,这样就可以获取@EnableXXX注解的一些信息了。

  1. 新建一个ImportSelectorImpl来实现ImportSelector接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14

    public class ImportSelectorImpl implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 获取注解集合
            MergedAnnotations annotations = importingClassMetadata.getAnnotations();
            // 获取TestImport注解信息
            MergedAnnotation<TestImport> testImportMergedAnnotation = annotations.get(TestImport.class);
            // 打印
            System.out.println("===="+testImportMergedAnnotation.getString("value"));
            return new String[]{"a.b.c.e.TestImportA","a.b.c.e.TestImportB"};
        }
    }

  1. 在启动类上改用@TestImport注解
1
2
3
4
5
6
7
8
9
10
11

    @SpringBootApplication
    @TestImport
    public class SpringbootOneApplication {
        public static void main(String[] args) {
            ConfigurableApplicationContext run = SpringApplication.run(SpringbootOneApplication.class, args);
            TestImportB testImportB = run.getBean(TestImportB.class);
            testImportB.test();
        }
    }

导入ImportBeanDefinitionRegistrar的实现类

这个用法和ImportSelector的用法是一样的,这里就不全展示了,直接在TestImport注解上换成@Import(ImportBeanDefinitionRegistrarImpl.class)就行了。

  1. 新建ImportBeanDefinitionRegistrar实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

    public class ImportBeanDefinitionRegistrarImpl implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // 获取注解集合
            MergedAnnotations annotations = importingClassMetadata.getAnnotations();
            // 获取TestImport注解信息
            MergedAnnotation<TestImport> testImportMergedAnnotation = annotations.get(TestImport.class);
            // 打印value属性值
            System.out.println("===="+testImportMergedAnnotation.getString("value"));
            // 注册信息
            registry.registerBeanDefinition("testImportB",new RootBeanDefinition(TestImportB.class));
        }
    }

最后

还记得最近的面试中,被问到了如何向spring容器中注册bean,当时我的回答是:

  1. @Component,@Service,@Controller,@Repository这四个注解,后三个底层都是用的@Component。
  2. @Configuration,在配置类中使用@Bean注册
  3. BeanFactoryAware, ApplicationContextAware,实现这两个接口,然后手动向容器中注册。

这次就可以另外加上一个@Import,ImportBeanDefinitionRegistrar以及ImportSelector了。

坚持原创技术分享,您的支持将鼓励我继续创作!