@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月前
|
消息中间件 监控 关系型数据库
覆盖迁移工具选型、增量同步策略与数据一致性校验
本文深入解析数据迁移核心挑战,涵盖工具选型、增量同步优化与一致性校验三大关键环节,结合实战案例与代码方案,助开发者规避风险,实现高效可靠迁移。
251 0
|
5月前
|
关系型数据库 MySQL Java
MySQL 分库分表 + 平滑扩容方案 (秒懂+史上最全)
MySQL 分库分表 + 平滑扩容方案 (秒懂+史上最全)
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
算法 调度
【完全复现】基于改进粒子群算法的微电网多目标优化调度
该文档描述了一个使用改进粒子群算法实现的微电网多目标优化调度的Matlab程序。该模型旨在最小化运行成本和环境保护成本,将多目标问题通过权值转换为单目标问题解决。程序中定义了决策变量,如柴油发电机、微型燃气轮机、联络线和储能的输出,并使用全局变量处理电负荷、风力和光伏功率等数据。算法参数包括最大迭代次数和种群大小。代码调用了`PSOFUN`函数来执行优化计算,并展示了优化结果的图表。
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must b
java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must b
1450 0
|
IDE 开发者 iOS开发
京东开源的 JD-Hotkey:高效热键管理的技术巅峰
【10月更文挑战第5天】在快节奏的工作与学习中,提高操作效率是每位技术爱好者不懈追求的目标。今天,我们将深入探讨京东开源的 JD-Hotkey 项目,它不仅是一个强大的热键管理工具,更是我们在日常工作中提升生产力的秘密武器。通过本文,你将了解到 JD-Hotkey 的核心功能、技术亮点以及在实际应用中的卓越表现,一同感受其带来的高效与便捷。
723 4
(详细图解过程) IDEA在创建类的的时候自动生成作者信息、时间等信息
这篇文章介绍了如何在IntelliJ IDEA中设置文件和代码模板,以便在创建新类时自动生成包含作者信息、日期和时间等信息的文件头。
(详细图解过程) IDEA在创建类的的时候自动生成作者信息、时间等信息
|
消息中间件 存储 负载均衡
RocketMQ 消息的顺序和重复
这篇文章探讨了RocketMQ中消息顺序和重复的问题,解释了为什么RocketMQ不保证消息顺序和不重复,并提供了解决这些问题的策略,包括消费端幂等性处理和使用日志表记录已处理消息ID,同时介绍了RocketMQ的事务消息、Producer和Consumer的最佳实践,以及其他配置和RocketMQ的基本概念。
427 0
RocketMQ 消息的顺序和重复
|
安全 Linux 网络安全
网络空间安全之一个WH的超前沿全栈技术深入学习之路(10-1):保姆级别教会你如何搭建白帽黑客渗透测试系统环境Kali——Liinux-Debian:就怕你学成黑客啦!)作者——LJS
保姆级别教会你如何搭建白帽黑客渗透测试系统环境Kali以及常见的报错及对应解决方案、常用Kali功能简便化以及详解如何具体实现

热门文章

最新文章