定义/作用

@Import注解只能作用在类上,一种使用场景是在spring注解驱动开发环境下与配置类配合使用的,其作用是引用其他配置类。使得我们可以和早起的基于XML配置文件开发那样。使用不同的配置类配置不同的内容,比如Mysql数据源配置用一个配置类。Redis数据源配置用一个配置类等。然后使用在注解在一个主配置类中引入这些从配置类,使得配置更加清晰。被引入的类可以不使用@Configuration、@Component注解。

另一种使用 场景是该注解也是一种注册bean的方案。可以在配置类中使用Import注册组件。可以配合ImportSelector、ImportBeanDefinitionRegistrar按一定规则进行组件的批量注册。

源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {/*** 要引入的配置类,也可以引入ImportSelector、ImportBeanDefinitionRegistrar过滤器和注册器* 按照一定的规则进行组件的引入。*/Class<?>[] value();}

使用方式:

不成功的情况:

/*** @author YeHaocong* @decription 主配置类*/@Configuration
public class SpringConfig {}/*** @author YeHaocong* @decription Mysql数据源配置类*/
@Configuration
public class MysqlConfig {@Beanpublic DruidDataSource dataSource() throws IOException {//创建druid数据源DruidDataSource dataSource = new DruidDataSource();//加载配置文件,作为数据源的初始化属性Properties properties = PropertiesLoaderUtils.loadAllProperties("daoconfig/datasource-config.properties");dataSource.setConnectProperties(properties);//返回dataSource,spring会把他注册到IOC容器中。return dataSource;}//.....
}//测试类
public class ImportDemoTest {//引入主配置类创建容器private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);@Testpublic void testImportDemo(){DataSource dataSource = (DataSource) context.getBean("dataSource");System.out.println(dataSource);}
}

配置文件:

执行结果:

分析:因为容器是引入主配置类创建,而没有引入mysql数据源配置类,所以不会扫描创建数据源。

使用Import注解解决:

/*** @author YeHaocong* @decription 主配置文件*/@Configuration
//使用import注解,把其他从配置类引入
@Import({MysqlConfig.class})
public class SpringConfig {}
/*** @author YeHaocong* @decription Mysql数据源配置文件*/
//从配置类可以不使用@Configuration和Component等注解。
public class MysqlConfig {@Beanpublic DruidDataSource dataSource() throws IOException {DruidDataSource dataSource = new DruidDataSource();//加载配置文件,作为数据源的初始化属性Properties properties = PropertiesLoaderUtils.loadAllProperties("daoconfig/datasource-config.properties");dataSource.setConnectProperties(properties);//返回dataSource,spring会把他注册到IOC容器中。return dataSource;}//.....
}

执行结果:

可见数据源配置类被成功引入,数据源成功创建。

被引入的类会被注册到spring的IOC容器中,并且组件id为类的全限定名称,比如上面的:


public class ImportDemoTest {private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);@Testpublic void testImportDemo(){//获取MysqlConfig配置组件MysqlConfig mysqlConfig = context.getBean(MysqlConfig.class);System.out.println(mysqlConfig);//获取注解中所有的组件名称String[] beanNames = context.getBeanDefinitionNames();for (String beanName:beanNames)System.out.println(beanName);}
}

结果:

ImportSelector和ImportBeanDefinitionRegistrar

我们注册bean的方式有很多种。
比如:

  1. 我们自己写的类,可以使用@Component及其衍生类进行注册。
  2. 到导入第三方库时,可以使用@Bean注解和@Import注解进行注册。
    但是,当要注册的类很多时,每个类上加注解,写Bean方法注册,用Import方法导入大量的Bean时,会显得很繁琐,此时可以使用自定义ImportSelector和ImportBeanDefinitionRegistrar来实现组件的批量注册。spring boot有很多EnableXXX的注解,绝大多数多借助了ImportSelector和ImportBeanDefinitionRegistrar。

共同点:

  • 他们都用于动态注册bean对象到容器中,并且支持大批量的bean导入。

区别:

  • ImportSelector是一个接口,我们在使用时需要提供自己的实现类,实现类中重写的方法返回要注册的bean的全限定名数组。然后ConfigurationClassParser类中的precessImports方法注册bean对象。
  • ImportBeanDefinitionRegistrar也是一个接口,需要我们自己提供实现类,在实现类中手动注册bean到容器中。

注意事项:实现了ImportSelector和ImportBeanDefinitionRegistrar的类不会被解析成一个bean添加到容器中。

ImportSelector

demo:
包结构:

代码:

/*** @author YeHaocong* @decription 自定义的ImportSelector,导入选择器。* 1. 通过AspectJ表达式进行类型筛选。* 2. 当使用该选择器的配置类没有使用@ComponentScan注解指定扫描包时,会扫描该配置类所在包及其子包。*/public class CustomImportSelector implements ImportSelector {//AspectJ表达式private String expression;public CustomImportSelector() throws IOException {try {//载入配置文件,创建一个Properties对象Properties props = PropertiesLoaderUtils.loadAllProperties("import/custom-import-selector.properties");//获取配置文件配置的键为 expression的值,并赋值给expression变量expression = props.getProperty("expression");if (expression == null || expression.isEmpty()){throw new RuntimeException("配置文件import/custom-import-selector.properties 的expression 不存在");}}catch (RuntimeException e){throw e;}}/**** @param importingClassMetadata 参数是被Import注解作用的配置类的注解元信息* @return 返回的是要注册的组件的类的全限定名数组。*/public String[] selectImports(AnnotationMetadata importingClassMetadata) {//定义要扫描的基础包String[] basePackages = null;//获取ComponentScan注解的全限定名称。String ComponentScanName = ComponentScan.class.getName();//判断被Import注解作用的类上是否有@ComponentScan注解if (importingClassMetadata.hasAnnotation(ComponentScanName)){//有@ComponentScan注解,获取该注解上的属性配置,封装成Map对象。Map<String,Object> attributes = importingClassMetadata.getAnnotationAttributes(ComponentScanName);//获取@ComponentScan注解的value属性或者basePackages属性,因为他们是互为别名,所以获取其中一个即可。basePackages = (String[]) attributes.get("basePackages");}//判断是否有ComponentScan注解或者ComponentScan注解是否有指定扫描包。//当basePackages为null时,表示没有ComponentScan注解。//当basePackages.length等于0时,表示有basePackages注解,但是没有指定扫描的包。if (basePackages == null || basePackages.length == 0){//如果@Import注解作用的配置类上没有ComponentScan注解或者有ComponentScan注解但是没有指定扫描包的情况下。//我们就扫描该配置类所在包及其子包。String basePackage = null;//获取被Import注解作用的配置类所在的包。try {basePackage = Class.forName(importingClassMetadata.getClass().getName()).getPackage().getName();} catch (ClassNotFoundException e) {e.printStackTrace();}//把包名设置到basePackages中。basePackages = new String[]{basePackage};}//创建类路径扫描器,参数的含义是不使用默认的过滤规则,与@ComponentScan注解的 useDefaultFilters属性一样。ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);//创建类型过滤器,此处使用AspectJ类型过滤器。传入参数是AspectJ表达式和类加载器对象TypeFilter typeFilter = new AspectJTypeFilter(expression,CustomImportSelector.class.getClassLoader());//类型过滤器添加到扫描器中。添加的是包含扫描器。scanner.addIncludeFilter(typeFilter);//定义要扫描类的全限定类名的集合Set<String> classes = new HashSet<>();//遍历基础扫描类数组,得到要扫描的类的全限定名,并添加到集合中for (String basePackage: basePackages){//扫描基础包,获取扫描到的BeanDefinition集合Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);//遍历。获取全限定名添加到集合中。for (BeanDefinition beanDefinition: candidateComponents){classes.add(beanDefinition.getBeanClassName());}}//返回集合return classes.toArray(new String[classes.size()]);}
}/**
*配置类
*/@Configuration
@Import({CustomImportSelector.class})
public class SpringConfig {}//还有两个业务接口和两个业务接口实现类和一个ConfigUtil,这两个业务实现类和ConfigUtil类都是要注册的组件。这里不再贴出,可以看上面包结构。public class TestImportSelector {private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);@Testpublic void TestImportSelector(){//根据类型获取beantry {ConfigUtil configUtil = (ConfigUtil) context.getBean(ConfigUtil.class);System.out.println(configUtil);}catch (Exception e){System.out.println(e.getMessage());}try {RoleService roleService = (RoleService) context.getBean(RoleService.class);System.out.println(roleService);}catch (Exception e){System.out.println(e.getMessage());}try {UserService userService = (UserService) context.getBean(UserService.class);System.out.println(userService);}catch (Exception e){System.out.println(e.getMessage());}}
}

配置文件:

执行结果:

分析:一个bean都没有注册成功,原因是:
配置类SpringConfig上没有使用@ComponentScan或者使用了但是没有配置扫描包。所以会扫描配置类所在包及其子包,看上面包结果。明显没有扫描到service包。所以两个业务实现类没有被注册到容器中。而ConfigUtil虽然被扫描到了,但是由于不符合AspectJ表达式而没有被添加到选择器中。

接下来我们使用@ComponentScan扫描指定包。

@Configuration
@ComponentScan(basePackages = "importselectdemo")
@Import({CustomImportSelector.class})
public class SpringConfig {}/**
*测试类
*/
public class TestImportSelector {private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);@Testpublic void TestImportSelector(){//根据类型获取beantry {ConfigUtil configUtil = (ConfigUtil) context.getBean(ConfigUtil.class);System.out.println(configUtil);}catch (Exception e){System.out.println(e.getMessage());}try {RoleService roleService = (RoleService) context.getBean(RoleService.class);System.out.println(roleService);}catch (Exception e){System.out.println(e.getMessage());}try {UserService userService = (UserService) context.getBean(UserService.class);System.out.println(userService);}catch (Exception e){System.out.println(e.getMessage());}try {CustomImportSelector selector = (CustomImportSelector) context.getBean(CustomImportSelector.class);System.out.println(selector);}catch (Exception e){System.out.println(e.getMessage());}String[] beanNames = context.getBeanDefinitionNames();for (String beanName:beanNames){System.out.println(beanName);}}
}

执行结果:

注意:不能导入配置类自身,因为,这样会导致报错。
将表达式设置为:
结果:

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: A circular @Import has been detected: Illegal attempt by @Configuration class 'SpringConfig' to import class 'SpringConfig' as 'SpringConfig' is already present in the current import stack [SpringConfig->SpringConfig]
Offending resource: importselectdemo.config.SpringConfig

使用上述方法只要符合CustomImportSelector规则,即使不使用@Component等注解也会注册到容器中。

ImportBeanDefinitionRegistrar

这个注册器不会把扫描到的类返回,而是把扫描到的类直接就在这里注册了。

demo(扫描逻辑与上面的CustomImportSelector一样):

/*** @author YeHaocong* @decription 自定义的ImportBeanDefinitionRegistrar,导入注册器。* 1. 通过AspectJ表达式进行类型筛选。* 2. 当使用该选择器的配置类没有使用@ComponentScan注解指定扫描包时,会扫描该配置类所在包及其子包。* 3. CustomImportDefinitionRegistrar会扫描指定包里,符合AspectJ表达式的组件的类进行注册*/public class CustomImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {//AspectJ表达式private String expression;public CustomImportDefinitionRegistrar() throws IOException {try {//载入配置文件,创建一个Properties对象Properties props = PropertiesLoaderUtils.loadAllProperties("import/custom-import-selector.properties");//获取配置文件配置的键为 expression的值,并赋值给expression变量expression = props.getProperty("expression");if (expression == null || expression.isEmpty()){throw new RuntimeException("配置文件import/custom-import-selector.properties 的expression 不存在");}}catch (RuntimeException e){throw e;}}/*** * @param importingClassMetadata  参数是被Import注解作用的配置类的注解元信息* @param registry   BeanDefinition注册器,会将扫描到的类直接使用该注册器进行注册*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//定义要扫描的基础包String[] basePackages = null;//获取ComponentScan注解的全限定名称。String ComponentScanName = ComponentScan.class.getName();//判断被Import注解作用的类上是否有@ComponentScan注解if (importingClassMetadata.hasAnnotation(ComponentScanName)){//有@ComponentScan注解,获取该注解上的属性配置,封装成Map对象。Map<String,Object> attributes = importingClassMetadata.getAnnotationAttributes(ComponentScanName);//获取@ComponentScan注解的value属性或者basePackages属性,因为他们是互为别名,所以获取其中一个即可。basePackages = (String[]) attributes.get("basePackages");}//判断是否有ComponentScan注解或者ComponentScan注解是否有指定扫描包。//当basePackages为null时,表示没有ComponentScan注解。//当basePackages.length等于0时,表示有basePackages注解,但是没有指定扫描的包。if (basePackages == null || basePackages.length == 0){//如果@Import注解作用的配置类上没有ComponentScan注解或者有ComponentScan注解但是没有指定扫描包的情况下。//我们就扫描该配置类所在包及其子包。String basePackage = null;//获取被Import注解作用的配置类所在的包。try {basePackage = Class.forName(importingClassMetadata.getClass().getName()).getPackage().getName();} catch (ClassNotFoundException e) {e.printStackTrace();}//把包名设置到basePackages中。basePackages = new String[]{basePackage};}//创建类路径扫描器ClassPathBeanDefinitionScanner,参数的含义是不使用默认的过滤规则,与@ComponentScan注解的 useDefaultFilters属性一样。//registry参数是将扫描到的类使用指定的registry注册器注册ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry,false);//创建类型过滤器,此处使用AspectJ类型过滤器。传入参数是AspectJ表达式和类加载器对象TypeFilter typeFilter = new AspectJTypeFilter(expression,CustomImportSelector.class.getClassLoader());//类型过滤器添加到扫描器中。添加的是包含扫描器。scanner.addIncludeFilter(typeFilter);//进行扫描scanner.scan(basePackages);}
}//配置类:
@Configuration
@ComponentScan(basePackages = "importselectdemo")
//使用CustomImportDefinitionRegistrar
@Import({CustomImportDefinitionRegistrar.class})
public class SpringConfig {}

结果:

解析:

  1. ConfigUtil不符合AspectJ表达式规则,所以没有注册。
  2. 业务类注册成功。
  3. 实现ImportBeanDefinitionRegistrar接口的类不会被添加到容器中。
  4. 因为使用的是BeanDefinitionRegistry注册器,所以注册的bean id 默认是类的名字第一个转小写。而不是全限定名称。

使用上述方法只要符合CustomImportDefinitionRegistrar规则,即使不使用@Component等注解也会注册到容器中。

Spring高级之注解@Import注解、ImportSelector、ImportBeanDefinitionRegistrar详解(超详细)相关推荐

  1. @Autowired注解详解——超详细易懂

    @Autowired详解 要搞明白@Autowired注解就是要了解它是什么?有什么作用?怎么用?为什么? 首先了解一下IOC操作Bean管理,bean管理是指(1)spring创建对象 (2)spr ...

  2. Spring data JPA使用详解(超详细)

    目录 Jpa 元模型 Criteria查询 CriteriaBuilder 安全查询创建工厂 CriteriaQuery 安全查询主语句 Root Predicate 过滤条件 Predicate 多 ...

  3. Spring基于注解TestContext 测试框架使用详解

    原创整理不易,转载请注明出处:Spring基于注解TestContext 测试框架使用详解 代码下载地址:http://www.zuidaima.com/share/1775574182939648. ...

  4. async spring 默认线程池_Spring boot注解@Async线程池实例详解

    这篇文章主要介绍了Spring boot注解@Async线程池实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 从Spring3开始提供了@A ...

  5. SpringAOP描述及实现_AspectJ详解_基于注解的AOP实现_SpringJdbcTemplate详解

    AOP AOP特点: 面向切面编程, 利用AOP对业务逻辑的各个部分进行抽取公共代码, 降低耦合度, 提高代码重用性, 同时提高开发效率. 采取横向抽取, 取代传统纵向继承体系重复性代码 解决事务管理 ...

  6. Spring Cloud:使用Ribbon实现负载均衡详解(下)

    在上一篇文章(Spring Cloud:使用Ribbon实现负载均衡详解(上))中,我对 Ribbon 做了一个介绍,Ribbon 可以实现直接通过服务名称对服务进行访问.这一篇文章我详细分析一下如何 ...

  7. 【夯实Spring Cloud】Spring Cloud中使用Hystrix实现断路器原理详解(上)

    本文属于[夯实Spring Cloud]系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习Spring Cloud技术,希望能给读者带来一些干货.系列目录如下: [夯实Spring Cloud]D ...

  8. 基于spark mllib_Spark高级分析指南 | 机器学习和分析流程详解(下)

    - 点击上方"中国统计网"订阅我吧!- 我们在Spark高级分析指南 | 机器学习和分析流程详解(上)快速介绍了一下不同的高级分析应用和用力,从推荐到回归.但这只是实际高级分析过程 ...

  9. Spring Cloud Eureka 入门 (二)服务提供者详解

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! "优秀不是过 ...

  10. vue高级进阶( 二 ) 8种组件通信详解

    猛兽总是独行,牛羊才成群结队. -------鲁迅 vue组件通信的重要性无需多言...但是你肯定没有全部掌握,所以这第二篇文章应运而生 props和$emit props父传子,$emit子传父,看 ...

最新文章

  1. css修改select下拉列表的默认样式
  2. 数字图像处理:第四章 点运算
  3. UIAutomation识别UI元素
  4. SpringMVC实现RESTful风格
  5. 2018.12.20 Spring环境如何搭建
  6. 牛客假日团队赛1 D.Promotion Counting
  7. Json for the .NET Compact Framework
  8. JavaScript 系列笔记(一)数据类型
  9. centos7修改ip地址命令_linux nmcli命令详解
  10. MT6763,MT6763T(P23),MT6750T,MT6755(P10)参数比较
  11. 收发短信AT指令详解
  12. 基于微信小程序办公用品采购系统的设计与实现.rar(毕业论文+程序源码+后端源码)
  13. windows系统安全
  14. 阿里高P谈技术人如何高速成长?
  15. 03【若依框架解读】Tree树形结构的控制(菜单,部门)
  16. crm客户管理系统如何助力企业销售管理
  17. miniconda的安装及使用
  18. “天成云”品牌发布——Tungsten Fabric助力开源开放生态发展
  19. 阿布扎比国家石油公司与埃尼和OMV签署炼油与贸易合作协议
  20. 钻石指标和完美的计算

热门文章

  1. 《天天数学》连载07:一月七日
  2. 大数据学习笔记02:在私有云上创建与配置虚拟机
  3. 【BZOJ3107】二进制a+b,DP
  4. 16.对极几何——介绍,立体视觉约束,条件_1
  5. 1.极限——ε-δ例子_7
  6. idea @value提示_IDEA 中springboot 项目使用 注解Autowired 出现红线
  7. python函数封装总结_python 笔记 之 函数封装成类
  8. 2017.3.3 双栈排序 失败总结
  9. java oauth2搭建_Spring Security 实战干货:OAuth2授权请求是如何构建并执行的
  10. 【英语学习】【科学】【Glencoe Science】【C】Animal Diversity 目录及术语表