@Import注解使用详解

简介: @Import注解使用详解
日积月累,水滴石穿 😄

@Import注解使用详解

@Import注解可以用来导入配置类,功能与 xml 中的 import 标签等效。

Import注解定义源码

/**
 * 可以导入的一个或多个 @Configuration 类
 Indicates one or more {@link Configuration @Configuration} classes to import.
 * @Import的功能与 xml 中的 <import/>标签等效
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
   允许导入 @Configuration 类 、ImportSelector 和 ImportBeanDefinitionRegistrar 的实现 以及导入普通类(4.2版本开始支持;类似 AnnotationConfigApplicationContext#register(java.lang.Class<?>)
 * 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>May be declared at the class level or as a meta-annotation.
 * 如果需要导入XML 或其他非bean 定义资源,请改用@ImportResource注解
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

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

}

Import注解源码的定义非常简单,就一个属性 value,而且是一个 Class 类型的数组。

结合上面的注释,对我们了解Import有很大的帮助。

  • 可以同时导入多个 @Configuration类 、ImportSelector 和 ImportBeanDefinitionRegistrar 的实现,以及导入普通类(4.2版本开始支持)
  • @Import的功能与 xml 中的 <import/>标签等效
  • 在类级别声明或作为元注释
  • 如果需要导入XML 或其他非bean 定义资源,请使用@ImportResource注解

支持的三种类型

  • 实现 ImportSelector 接口的类
  • 实现 ImportBeanDefinitionRegistrar 接口的类
  • 不是上述两种情况,将类作为配置类处理。

接下来小杰带大家来看看使用过程。

代码测试

@Import导入普通类

  • 提供 ImportTest类,该类没有任何注解标注
public class ImportTest {

    public void importTest(){
        System.out.println("ImportTest === >");
    }
}
  • 扫描并使用 @Import导入 ImportTest类,进行启动
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportTest.class})
public class AppMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
        // 从容器中获得 ImportTest 
        ImportTest bean = context.getBean(ImportTest.class);
        bean.test();

    }
}

结果:
ImportTest === >   

@Import导入配置类

这里说的配置类是被 @Configuration标注的类。

@Configuration
public class ImportTest {

    public void test(){
        System.out.println("ImportTest === >");
    }
}
  • 启动结果和导入普通类一致。区别在于 @Configuration标注的类会被 CGLIB 进行代理

image-20220125200230766

实现 ImportSelector

基本使用

  • 创建 ImportSelectorComponent类,该类会被导入。
public class ImportSelectorComponent {

    public void test(){
        System.out.println("ImportSelectorComponent === >");
    }
}
  • 创建 ImportSelectorTest类,实现 ImportSelector接口,并重写 selectImports方法,方法返回的是需要导入类的全限定名的数组。
public class ImportSelectorTest implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //return new String[]{"com.cxyxj.importdemo.ImportSelectorComponent"};
        return new String[]{ImportSelectorComponent.class.getName()};
    }
}
  • @Import导入的值修改为 ImportSelectorTest.class,并进行启动
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportSelectorTest.class})
public class AppMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
        //这里也要修改:获得 ImportSelectorComponent
        ImportSelectorComponent bean = context.getBean(ImportSelectorComponent.class);
        bean.test();
         // 打印 bean 名称
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name : beanDefinitionNames){
            System.out.println(name);
        }
    }
}

结果:
ImportSelectorComponent === >
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
appMain
com.cxyxj.importdemo.ImportSelectorComponent

注意被ImportSelector导入的类的 beanName 是类的全限定名称,实现 ImportSelector 的类不会被注入容器中。

效果达到之后,你会发现这种使用方式有点繁琐,还要写一个实现类。觉得并没有什么用,直接使用 import直接导入不更优雅吗。的确是的,如果是导入固定的 Bean 定义,这种方式肯定不是最优的。所以它的用途在其他方向,比如需要进行一些逻辑处理之后,进行动态注册时,该方式是非常有用的。在 SpringSpring Boot底层大量使用该方式进行导入Bean定义。

源码定义


/**
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or
 * more annotation attributes.
 *
 * <p>An {@link ImportSelector} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces,
 * and their respective methods will be called prior to {@link #selectImports}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
 * </ul>
 *
 * <p>{@code ImportSelector} implementations are usually processed in the same way
 * as regular {@code @Import} annotations, however, it is also possible to defer
 * selection of imports until all {@code @Configuration} classes have been processed
 * (see {@link DeferredImportSelector} for details).
 *
 * @author Chris Beams
 * @since 3.1
 * @see DeferredImportSelector
 * @see Import
 * @see ImportBeanDefinitionRegistrar
 * @see Configuration
 */
public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

ImportSelector接口的源码也是很简单的。接口中就一个selectImports方法。方法的参数是AnnotationMetadata,通过该参数我们可以获得被 @Import标注的类的各种信息,比如:Class名称,实现的接口名称、父类名称、类上的其他注解信息。

代码示例小杰就不写了,我们看看 Spring的处理方式。找到ImportSelector接口的抽象子类 AdviceModeImportSelector,当我们在启动类上加上 @EnableAsync或者@EnableCaching就会进行到这里。

代码意思也不阐述了,打个断点跑一遍就清楚了。

image-20220125200335811

我们来看看接口上的注释,大意如下:

由类实现的接口,可以导入一个或者多个类。
该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware、ResourceLoaderAware,那么在调用其 selectImports 方法之前先调用上述接口中对应的方法。
ImportSelector实现的处理方式通常与常规的@Import注解相同,如果想延迟导入,可以使用 ImportSelector 的子接口 DeferredImportSelector,它会在所有 @Configuration类都被处理完之后,才开始进行导入。    

DeferredImportSelectorSpringBoot自动装配有很大的关系,这个到Spring Boot系列再说。

实现 ImportBeanDefinitionRegistrar

基本使用

  • 创建 ImportBeanDefinitionRegistrarComponent普通类
public class ImportBeanDefinitionRegistrarComponent {

    public void test(){
        System.out.println("ImportBeanDefinitionRegistrarComponent === >");
    }
}
  • 创建 ImportBeanDefinitionRegistrarTest,实现 ImportBeanDefinitionRegistrar接口,并重写 registerBeanDefinitions方法
public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // containsBeanDefinition:判断容器中是否存在指定的 bean 定义,true 存在
        boolean b = registry.containsBeanDefinition(ImportBeanDefinitionRegistrarComponent.class.getName());
        if (!b){
            RootBeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarComponent.class);
            //注册BeanDefinition,并指定 beanName
            registry.registerBeanDefinition("registrarComponent", beanDefinition);
        }
    }

}
  • @Import导入的值修改为 ImportBeanDefinitionRegistrarTest.class,并进行启动
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportBeanDefinitionRegistrarTest.class})
public class AppMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
        //这里也要修改:获得 ImportBeanDefinitionRegistrarComponent
        ImportBeanDefinitionRegistrarComponent bean = context.getBean(ImportBeanDefinitionRegistrarComponent.class);
        bean.test();

        // 打印 bean 名称
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name : beanDefinitionNames){
            System.out.println(name);
        }
    }
}

结果:
ImportBeanDefinitionRegistrarComponent === >

注意被ImportBeanDefinitionRegistrar注册的类的 beanName 是可以自定义的,实现 ImportBeanDefinitionRegistrar 的类不会被注入容器中。

源码释义跟 ImportSelector差不多,就不在阐述。

自定义注解

接下来小杰将使用 ImportBeanDefinitionRegistrar实现,添加了我们自定义的@Cxyxj注解的类会被自动加入到Spring容器中。

  • 自定义注解 Cxyxj
/**
 * 自定义注解
 * @author cxyxj
 */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Cxyxj {
}
  • 修改 ImportBeanDefinitionRegistrarTest类的代码,修改如下:
public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获得 ComponentScan 注解
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
        // 获得 basePackages 的信息
        String[] basePackages = (String[]) annotationAttributes.get("basePackages");
        // 扫描器,这也是Spring底层扫描使用的
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        // 满足任意 includeFilters的会被扫描
        scanner.addIncludeFilter(new AnnotationTypeFilter(Cxyxj.class));
        // 继续扫描
        scanner.scan(basePackages);
    }

}
  • 启动类
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportBeanDefinitionRegistrarTest.class})
public class AppMain {

    public static void main(String[] args) {
        
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);

        CxyxjTest bean = context.getBean(CxyxjTest.class);
        bean.cxyxjTest();
        // 打印 bean 名称
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name : beanDefinitionNames){
            System.out.println(name);
        }
    }
}

image-20220125200316805

该效果使用 ImportSelector也是可以实现的。


  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
6月前
|
Java
【Java】注解(Annotation)
【Java】注解(Annotation)
47 0
|
Java Spring 容器
Springboot中的@Import注解~
Springboot中的@Import注解~
|
XML 缓存 SpringCloudAlibaba
Spring注解导入:@Import使用及原理详解
`@Import` 是 Spring 基于 Java 注解配置的主要组成部分,`@Import` 注解提供了类似 `@Bean` 注解的功能,向Spring容器中注入bean,也对应实现了与Spring XML中的<import/>元素相同的功能
844 0
|
Java 测试技术 程序员
Java的注解(Annotation)
Java的注解(Annotation)
133 0
|
Java
Java 注解(Annotation)
Java 注解(Annotation)
116 0
|
Java Spring 容器
Spring注解@Import使用
Spring注解@Import使用
Spring注解@Import使用
Java中lombok @Builder注解使用详解
Java中lombok @Builder注解使用详解
|
Java 编译器 API
19 浅析 Java 注解(Annotation)
Java 5之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),例如在方法覆盖中使用过的@Override注解,注解都是@符号开头的。 注解并不能改变程序运行的结果,不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
165 0
19 浅析 Java 注解(Annotation)
|
Java 编译器 程序员
Java-注解(@Annotation)
注解是插入到代码中的元数据,JDK5.0以后的版本引入。注解必须有编译器或者虚拟机来解析它,才能发挥自己的作用,它可以生成文件,可以执行编译时进行测试和验证格式等等。因为本质上,注解是一种特殊的接口,程序可以通过反射来获取指定程序元素的注解对象,然后通过注解对象来获取注解里面的元数据。 注解的作用: 1.编写文档:通过代码里标识的元数据生成文档; 2.代码分析:通过代码里标识的元数据对代码进行分析; 3.编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查;
|
Java 开发者 Spring
Java的自带注解Annotation(一)
Java的自带注解Annotation(一)
140 0
Java的自带注解Annotation(一)