目的

前面的文章@Bean源码解析有说到,将一个bean,转换成BeanDefinition,并存入BeanDefinitionMap有三种方式;本文,我们要说的是@Import注解这种方式;
@Import是用来引入bean的;和本文相关的,我们需要关注ImportSelector和ImportBeanDefinitionRegistrar

接口说明

ImportBeanDefinitionRegistrar该接口,定义了一个public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

该方法的实现类,可以完成将bean添加到beanDefinitionMap中;因为在该方法中,可以获取到BeanDefinitionRegistry,只是:如果我们程序员自己去提供一个该接口的实现类,需要我们自己在接口实现类中,声明一个beanDefinition对象;并给beanDefinition对象设置属性
也可以在该方法中,修改已经存在于BeanDefinitionMap中的beanDefinition对象;

ImportSelector
该接口定义的方法,返回的是一个数组集合,我们只需要在实现类中定义我们要注入的bean的全类名,并放到return的集合中,即可;springboot的自动注入就是利用这个原理的,
通过SpringFactoriesLoader.loadFactoryNames获取到所有jar包中定义的要自动注入的bean对应的全类名;然后,spring会对所有要注入的bean进行解析;

源码

我前面的博客,有说到过,所有将bean转换为BeanDefinition的操作,都是在

org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

这个方法中完成的,我们本次要说的@Import注解的处理,也是在这个方法内部完成的;


我们直接来看processImports(configClass, sourceClass, getImports(sourceClass), true);这行代码完成的动作;因为这个方法是本篇文章所要关注的

解析Import注解

getImports(sourceClass):这个方法内部嵌套的比较简单,就是获取到当前配置类上所有的注解,然后获取到@Import注解对应的value,也即:import注入的类

我们接着来看org.springframework.context.annotation.ConfigurationClassParser#processImports这个方法的处理:
先说这个方法的整体处理逻辑:

Import注解引入的类,有三种情况

  1. ImportSelector:
    1.1 判断Import引入的ImportSelector的实现类是否是DeferredImportSelector的实现类;DeferredImportSelector这个接口,在springboot自动注入的时候有用到;可以看下图1 AutoConfigurationImportSelector的继承关系:

    1.2 如果是非DeferredImportSelector的实现类,就正常处理:ImportSelector的实现类中,return的全类名数组; 在这里, 会依次解析每个类,防止要注入的类中有ImportSelector和ImportBeanDefinitionRegistrar的实现类;
    对于ImportSelector实现类,返回的普通bean存放到了configurationClasses中 会在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(java.util.Set)方法中初始化,完成bean到BeanDefinition的转换

  2. ImportBeanDefinitionRegistrar:
    这里的处理比较简单;将实现类的registrar对象和metadata存入到importBeanDefinitionRegistrars这个map中,也是在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(java.util.Set)进行beanDefinition对象的生成

  3. 普通类(bean):
    存入到了configurationClasses这个集合中;如果ImportSelector实现类返回的bean就是一个普通bean,那就会在第一次递归调用processImports的时候,在第三步这里添加到集合中;否则,会一直递归调用,直到解析到import的是普通bean;

也即:对于ImportBeanDefinitionRegistrar的实现类,直接存入到了map集合中;对于ImportSelector的实现类,需要在解析return的所有bean,防止return的bean中还有ImportSelector或者ImportBeanDefinitionRegistrar的实现类

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 {/*** 在处理import的时候有三种类型*   ImportSelector:*         ImportSelector的实现类中,return的全类名数组; 在这里 会依次解析每个类,防止类中有ImportSelector和ImportBeanDefinitionRegistrar的实现类*         返回的bean存放到了configurationClasses中  在前面的org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(java.util.Set)方法中初始化**  ImportBeanDefinitionRegistrar:*      该接口的实现类,存放到了importBeanDefinitionRegistrars中*  普通的类(bean):*    存入到了configurationClasses这个集合中**/for (SourceClass candidate : importCandidates) {if (candidate.isAssignable(ImportSelector.class)) {// Candidate class is an ImportSelector -> delegate to it to determine importsClass<?> candidateClass = candidate.loadClass();ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);//序号1if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));}else {String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);processImports(configClass, currentSourceClass, importSourceClasses, false);}}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitions//序号2Class<?> candidateClass = candidate.loadClass();ImportBeanDefinitionRegistrar registrar =BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);ParserStrategyUtils.invokeAwareMethods(registrar, this.environment, this.resourceLoader, this.registry);configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}else {// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->// process it as an @Configuration classthis.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());// 序号3processConfigurationClass(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();}}
}

这是方法的源码,在该方法的入参中,有一个参数比较重要:importCandidates,这个参数是Import注解对应的value;在解析ImportSelector实现类返回的bean时会用到这个参数

可以看到:

  1. 首先会先判断import注解引入的类是否是空
  2. 这里是一个校验,暂时没有搞明白
  3. 对ImportSelector的实现类进行处理
    3.1 判断ImportSelecot的实现类是否是:DeferredImportSelector的实现类
    3.2 如果不是DeferredImportSelector的实现类,就进行解析,获取到ImportSelector实现类中返回的所有bean
    3.3 对返回的所有bean进行一次解析;解析就递归调用processImports即可,这样,会对ImportSelector实现类返回的所有bean进行一次解析,防止返回的类中,还有ImportSelector或者ImportBeanDefinitionRegistrar的实现类
  4. 对ImportBeanDefinitionRegistrar实现类,进行处理;将实现类的对象和metadata存入到importBeanDefinitionRegistrars这个map中该接口的实现类;在后面会根据这个集合中的方法进行处理和解析
  5. 对普通bean进行处理;这这里,会对普通bean进行一次解析(这里所谓的解析,就是对bean的@ComponentScan、@Import、@Bean等注解进行解析,防止这里的bean是一个配置类),然后添加到configurationClasses这个map中

这里我一直在说的一个概念,就是递归重复解析ImportSelector实现类返回的所有bean,意思是这样的:

在这个gif中,是我写的一个demo,可以看到,在配置类中,我们通过@Import注解引入了AImportSelector这个类;然后AImportSelector类中,返回的值中有BImportSelector;在BImportSelector中返回了真正的业务类,这就是我上面所说的需要递归解析的原因;直到解析到UserBean这个业务类;可以在最后的输出日志中看到,我获取到所有的单实例bean,其中有userBean;这也证实了我刚才说的递归调用

这个方法,简单而言:最终完成了两个事情:
1.将ImportBeanDefinitionRegistrar的实现类,存入到了importBeanDefinitionRegistrars这个map中;在上面的第四步完成
2.将ImportSelector返回的类,进行递归解析,直到解析到普通的业务bean,然后将所有的业务bean,存到了configurationClasses这个map;在上面的第五步完成

处理解析之后bean

我们需要关注的代码是

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
在这个方法中,我们上面所说的解析,都是在parser.parse(candidates);这个方法中完成的;在调用完这个方法之后,会执行本方法中的this.reader.loadBeanDefinitions(configClasses);这里入参的configClasses,可以看到是从
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());这里获取的
这里点击去,就会发现,获取的是configurationClasses.keySet();

这里需要注意的是:configurationClasses这个map,我们上面说第五步的普通业务bean,会存入到这里;被解析的配置类也会放到这里;

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();for (ConfigurationClass configClass : configurationModel) {loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);}
}private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {if (trackedConditionEvaluator.shouldSkip(configClass)) {String beanName = configClass.getBeanName();if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {this.registry.removeBeanDefinition(beanName);}this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());return;}//这里是对importSelector注入的bean进行初始化if (configClass.isImported()) {registerBeanDefinitionForImportedConfigurationClass(configClass);}//@Bean 注解需要注入的bean对象for (BeanMethod beanMethod : configClass.getBeanMethods()) {loadBeanDefinitionsForBeanMethod(beanMethod);}//对importResource注解 注入的配置文件进行处理loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());//这里是对ImportBeanDefinitionRegistrar注入的bean进行初始化loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

这里的源码是loadBeanDefinitions的源码:我们可以看到,

  1. 遍历configurationModel
  2. 在loadBeanDefinitionsForConfigurationClass中,对configurationCLass进行判断
  3. 如果是Imported:就表示是通过Import引入的普通bean,会对该bean进行处理;在上面存入到map的时候,value是对应的元数据,在这里会根据元数据,生成beanDefinition对象
  4. 如果是@Bean注入的,会对bean对象进行处理,生成beanDefinition对象
  5. 对ImportBeanDefinitionRegistrar的实现类进行处理,这里的处理方式就是:循环调用实现类的registerBeanDefinitions方法

截止到这里,就完成了对ImportSelector和ImportBeanDefinitionRegisrar实现类的处理

spring源码:@Import注解相关推荐

  1. Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析

      我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...

  2. Spring源码学习第七天==>解析配置注解类与BPP

    关键词: Spring解析配置类注解Bean Spring注册Bean后置增强器(BPP) Spring消息资源和监听器的初始化 一:Spring解析配置类注解Bean==>Configurat ...

  3. Spring源码解析之@Component注解的扫描

    阅读须知 Spring源码版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文 承接Spring源码解析之context:component-scan标签解析,下面就是扫描的流程: Cl ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  7. Spring源码系列(十三)——Spring源码编译及详细注解

    文章目录 1. 环境搭建 2. 代码编译 2.1 编译代码 2.1.1 build.gradle 2.1.1.1 第一处 2.1.1.2 第二处 2.1.2 gradle.properties 2.1 ...

  8. spring 源码 找不到 taskprovider_Spring 源码阅读环境的搭建

    " 前言 本文记录了 Spring 源码环境的搭建方式,以及踩过的那些坑!​当前版本:5.3.2-SNAPSHOT. 环境准备 Git JDK master 分支需要 JDK 11 5.2. ...

  9. 我该如何学习spring源码以及解析bean定义的注册

    如何学习spring源码 前言 本文属于spring源码解析的系列文章之一,文章主要是介绍如何学习spring的源码,希望能够最大限度的帮助到有需要的人.文章总体难度不大,但比较繁重,学习时一定要耐住 ...

  10. 剑指Spring源码(一)

    Spring,相信每个Java开发都用过,而且是每天都在用,那强大又神秘的IoC,AOP,让我们的开发变得越来越简单,只需要一个注解搞定一切,但是它内部到底是什么样子的呢?跟着我,一起探究Spring ...

最新文章

  1. RTFNet:基于可见光/红外图像的城市自动驾驶道路场景语义分割
  2. 放张载玻片就能放大一万倍,普通光学显微镜都馋哭了 | Nature子刊
  3. COJ 1170 A Simple Problem
  4. java azure blob 查询_快速入门:适用于 Java 的 Azure Blob 存储客户端库 v8 | Microsoft Docs...
  5. 雅虎失败原因:没有跟上互联网变化节奏
  6. JS使用技巧2——momentjs太重了吗?试试dayjs和miment吧
  7. 地址修改验证TAR Oracle部署
  8. 在一线城市做Java开发如何月薪达到两万,需要技术水平达到什么程度?
  9. The 2014 ACM-ICPC Asia Regional Anshan
  10. 全国哀悼日网站都成黑白色实现
  11. 使用java代码画一棵圣诞树
  12. 海康威视2022内推 内推码
  13. image not loaded  try to open it externally to fix format problem
  14. 数字图像处理-高反差保留算法
  15. 芬兰政府:要找到量子计算工业化的好时机
  16. AMD机器:Android Studio启动模拟器提示“HAXM is not installed”的解决办法
  17. mybatis-源码
  18. RAR压缩包如何加密,忘记密码如何找回?
  19. android渲染是skia与egl,opengl和skia哪个快 游戏电脑问题解决分享!
  20. jquery php验证手机号码,使用jQuery如何实现手机号正则验证输入

热门文章

  1. TSAP(1) : DateTimes
  2. 使用案例_Excel中LOOKUP函数的使用案例
  3. 分治法实现最大子数组
  4. Android Studio新建工程syncing失败;Android studio Connection timed out: connect
  5. Ubunu16.04安装CPU版本Tensorflow
  6. 基础集合论 第一章 3 集合论的公式和条件
  7. Raki的读paper小记:Star-Transformer
  8. Raki的读paper小记:NATURAL LANGUAGE INFERENCE OVER INTERACTION SPACE
  9. Windows/Linux 下启动Kafka,外带安装包
  10. java里面的内存机制_Stack vs. Heap:了解 Java 的内存分配机制