Spring注解之@Import使用方法讲解

 更新时间:2023年01月03日 09:24:06   作者:程序员小潘  
@Import是Spring基于Java注解配置的主要组成部分,下面这篇文章主要给大家介绍了关于Spring注解之@Import的简单介绍,文中通过示例代码介绍的非常详细,需要的朋友可以参考下

1. 前言

Spring提供了@Import注解,用于向容器引入我们自定义的类,在许多注解中我们都会看到它的身影,比如MyBatis在整合Spring时,提供的@MapperScan注解:

@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}

比如Spring开启AOP功能的注解:

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
}

再比如Spring的异步调用注解:

@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}

大部分@Enable***注解的原理都差不多,都是通过利用@Import注解向Spring容器注入一些bean来实现的。

2. 用法

@Import注解可以引入三种类。

1、引入普通类,将作为bean注册到Spring容器。

@Configuration
@Import(Person.class)
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        Person person = context.getBean(Person.class);
    }
}

2、引入ImportSelector实现类,Spring会将**selectImports()**方法返回的bean数组注册到Spring容器,但是ImportSelector对象本身不会注册到容器。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.javap.importt.Person"};
    }
}

3、引入ImportBeanDefinitionRegistrar实现类,Spring会暴露 BeanDefinitionRegistry对象,你可以自由的往容器里面注册BeanDefinition,但是ImportBeanDefinitionRegistrar对象本身不会注册到容器。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 往registry注册BeanDefinition
    }
}

MyBatis整合Spring时,提供的@MapperScan注解引入的就是ImportBeanDefinitionRegistrar的实现类,MyBatis会去扫描指定的包路径,然后将Mapper接口对应的BeanDefinition注册到容器里。

3. 源码分析

解析类上的@Import注解的职责Spring交给了ConfigurationClassPostProcessor类,它是BeanFactoryPostProcessor的子类,也就是说它属于BeanFactory的扩展点之一,它会处理容器内所有的ConfigurationClass。

ConfigurationClassPostProcessor首先会过滤出容器内所有的ConfigurationClass,然后实例化一个ConfigurationClassParser解析器去解析ConfigurationClass,解析过程中就会处理类上的@Import注解了,方法是ConfigurationClassParser#processImports()

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    if (importCandidates.isEmpty()) {
        return;
    }
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    } else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {
                    // 引入的是ImportSelector子类
                    Class<?> candidateClass = candidate.loadClass();
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);
                    if (selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    } else {
                        // 调用子类方法,得到要引入的beanNames
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                        processImports(configClass, currentSourceClass, importSourceClasses, false);
                    }
                } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // 引入的是ImportBeanDefinitionRegistrar子类
                    Class<?> candidateClass = candidate.loadClass();
                    // 实例化子类对象
                    ImportBeanDefinitionRegistrar registrar =
                            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            registrar, this.environment, this.resourceLoader, this.registry);
                    // 暂存到Map,稍后触发
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                } else {
                    // 引入的是普通类,正常注入到容器
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    // 引入的类可能是ConfigurationClass,递归处理
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                            configClass.getMetadata().getClassName() + "]", ex);
        } finally {
            this.importStack.pop();
        }
    }
}

方法做了三件事:

  • 如果引入的是ImportSelector子类,实例化子类对象,调用selectImports()方法得到要注册的beanNames,完成注册。
  • 如果引入的是ImportBeanDefinitionRegistrar子类,实例化子类对象,暂存到Map,稍后触发。
  • 如果引入的是普通类,正常注册到容器。

对于ImportBeanDefinitionRegistrar子类,这里只是实例化了子类对象然后暂存到Map容器中,此时还没有去触发ImportBeanDefinitionRegistrar#registerBeanDefinitions()方法。方法的触发是在ConfigurationClass解析完以后,ConfigurationClassPostProcessor会调用ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars()方法遍历Map容器中所有的ImportBeanDefinitionRegistrar子类对象,挨个触发扩展方法。

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
            registrar.registerBeanDefinitions(metadata, this.registry));
}

到此这篇关于Spring注解之@Import使用方法讲解的文章就介绍到这了,更多相关Spring注解@Import内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅谈Java基于Consul创建分布式锁

    浅谈Java基于Consul创建分布式锁

    这篇文章主要介绍了浅谈基于Consul创建分布式锁,Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置Consul是分布式的、高可用的、可横向扩展的,需要的朋友可以参考下
    2023-07-07
  • java equals和==的区别详解

    java equals和==的区别详解

    这篇文章主要介绍了java equals和==的区别详解的相关资料,需要的朋友可以参考下
    2016-10-10
  • Java线程生命周期图文详细讲解

    Java线程生命周期图文详细讲解

    在java中,任何对象都要有生命周期,线程也不例外,它也有自己的生命周期。线程的整个生命周期可以分为5个阶段,分别是新建状态、就绪状态、运行状态、阻塞状态和死亡状态
    2023-01-01
  • JAVA宝藏工具hutool的使用

    JAVA宝藏工具hutool的使用

    开发过程中总是会遇到需要自己自定义工具类的情况,做一些数据转换、字符串操作、日期处理、加解密、编解码、金额计算等,本文就详细的介绍有一个工具类hutool的使用,感兴趣的可以了解一下
    2021-10-10
  • Java 生成PDF文档的示例代码

    Java 生成PDF文档的示例代码

    这篇文章主要介绍了Java 生成PDF文档的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • java多线程Future和Callable类示例分享

    java多线程Future和Callable类示例分享

    JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。今天我们就来研究下Future和Callable的实现方法
    2016-01-01
  • 解决grails服务端口冲突的办法(grails修改端口号)

    解决grails服务端口冲突的办法(grails修改端口号)

    grails中默认的服务端口为8080,当本机中需要同时启动两个不同的项目时,就会造成端口冲突,下面给出解决方法
    2013-12-12
  • 快速排序算法原理及java递归实现

    快速排序算法原理及java递归实现

    快速排序 对冒泡排序的一种改进,若初始记录序列按关键字有序或基本有序,蜕化为冒泡排序。使用的是递归原理,在所有同数量级O(n longn) 的排序方法中,其平均性能最好。就平均时间而言,是目前被认为最好的一种内部排序方法
    2014-01-01
  • 大数据 java hive udf函数的示例代码(手机号码脱敏)

    大数据 java hive udf函数的示例代码(手机号码脱敏)

    这篇文章主要介绍了大数据 java hive udf函数(手机号码脱敏),的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • SpringBoot使用minio进行文件管理的流程步骤

    SpringBoot使用minio进行文件管理的流程步骤

    MinIO 是一个高性能的对象存储系统,兼容 Amazon S3 API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文给大家介绍了SpringBoot使用minio进行文件管理的流程步骤,需要的朋友可以参考下
    2025-01-01

最新评论