Spring之@Import注解使用和spring源码分析

目录

1.前言

2. 本文结构

3. 三种import注解使用

3.1. @Import一个普通类

3.2. 实现了ImportBeanDefinitionRegistrar接口

3.3. 实现了ImportSelector

3.4. DeferredImportSelector

3.5. 注意项

4. 从Spring源码了解@Import的过程

4.1. AbstractApplicationContext.refresh()

4.2. invokeBeanFactoryPostProcessors()调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()

4.3. 进入ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry()

4.4. 进入ConfigurationClassPostProcessor类.processConfigBeanDefinitions()

4.4.1. ConfigurationClassUtils

4.4.2. 进入ConfigurationClassParser类.parse(Set)

4.4.3. processDeferredImportSelectors()

4.5. 进入ConfigurationClassPostProcessor类的 reader.loadBeanDefinitions(configClasses)


1.前言

最近经常用到@Import注解,让我对他的底层源码实现很感兴趣,同时我衍生了一系列思考,如

1.@Configuration,@Component,@Import,@ImportResouce,@Service, @ComponentScan @PropertySource,这些注解跟@Import注解有什么本质上的异同点?@ComponentScan是属于扫描包下面的配置类并注册成beanDefinition,而@Configuration,@Component本身就是配置类,那他们解析和注册配置类的顺序又是什么?

2.在spring底层处理中是通过单纯的if-else模式,还是策略模式来处理这一系列注解?

2. 本文结构

1.四种@Import注解用法列举

2.spring源码流程图

3.分析spring源码流程并讲述四种import注解的原理以及bean注入方式

3. 三种import注解使用

3.1. @Import一个普通类

spring会将该类加载到spring容器中

public class TestClass {

   public void test() {
      System.out.println( "test案例方法" );
   }
}

@Import(TestClass.class)
public class ImportConfig {

}

3.2. 实现了ImportBeanDefinitionRegistrar接口

在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry bd的注册器,能手工往beanDefinitionMap中注册 beanDefinition

public class TestRegistry {

   public void test() {
      System.out.println( "TestRegistry test()方法 案例" );
   }
}
public class ImportRegistry implements ImportBeanDefinitionRegistrar {

   @Override
   public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry ) {
      RootBeanDefinition bd = new RootBeanDefinition();
      bd.setBeanClass( TestRegistry.class );
      registry.registerBeanDefinition( "myClassRegistry", bd );
   }

}

@Import(ImportRegistry.class)
public class ImportConfig {

}

3.3. 实现了ImportSelector

重写selectImports方法该方法返回了String[]数组的对象,数组里面的类都会注入到spring容器当中

public class TestImport {
   public void test() {
      System.out.println( "TestImport test方法" );
   }
}

public class TestImportSelector implements ImportSelector {

   @Override
   public String[] selectImports( AnnotationMetadata importingClassMetadata) {
      return new String[] {TestImport.class.getName()};
   }
   
}

@Import(TestImportSelector.class)
public class ImportConfig {

}

3.4. DeferredImportSelector

用于springboot的自动装配,这里不做详细描述,后面会写相关文档

3.5. 注意项

假设有@Import(A.class)

1.如果 A类实现了DeferredImportSelector接口(DeferredImportSelector是ImportSelector的子类),spring容器就会实例化A类,并且调用其selectImports方法。而DeferredImportSelector实例的selectImports方法调用时机晚于ImportSelector的实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用,下面代码会说明

2.如果 A类实现了ImportBeanDefinitionRegistrar接口,那么 spring容器就会实例化A类,并且调用其registerBeanDefinitions方法

3.如果 A类没有实现ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一个,那么 spring容器就不会实例化A类

4. 从Spring源码了解@Import的过程

先奉上全局流程图

看不清没关系,奉上高清链接

ProcessOn FlowchartProcessOn是一个在线协作绘图平台,为用户提供强大、易用的作图工具!支持在线创作流程图、思维导图、组织结构图、网络拓扑图、BPMN、UML图、UI界面原型设计、iOS界面原型设计等。同时依托于互联网实现了人与人之间的实时协作和共享。https://www.processon.com/embed/644f20597cacb139ebbb31c1

4.1. AbstractApplicationContext.refresh()

先请上我们的主角spring容器,它的初始化代码是AbstractApplicationContext的refresh()方法,它里面调用了invokeBeanFactoryPostProcessors(),这个是我们的入口

4.2. invokeBeanFactoryPostProcessors()调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()

进入invokeBeanFactoryPostProcessors()方法

上面红线圈出来的意思是,只有ConfigurationClassPostProcessor类满足既实现了BeanDefinitionRegistryPostProcessor接口,又实现了PriorityOrdered接口的条件!!!

所以invokeBeanDefinitionRegistryPostProcessors()方法将调用ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法

展示一下上面代码的流程图:

4.3. 进入ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry()

因为只有ConfigurationClassPostProcessor类符合条件,所以进入ConfigurationClassPostProcessor类里面

进入后,继续看核心代码processConfigBeanDefinitions()

4.4. 进入ConfigurationClassPostProcessor类.processConfigBeanDefinitions()

下面代码流程图:

核心代码如下(下面几页代码都是同一个方法的,内容太多分开截图):

1.获取所有的beanName

2.看BeanDefinition中是否有处理完成的标识。没有处理完成的标识则在checkConfigurationClassCandidate中判断是否是候选的需要处理的BeanDefinition,并且set标识进入beanDefinition中,如果是就放入容器configCandidates。

3.创建一个配置类解析器对象,并调用parse方法解析配置类的代码,解析出来的配置类的数据放在ConfigurationClassParser类里面的属性值-Set<ConfigurationClass>中 ,且返回这个set集合,每个ConfigurationClass元素代表一个配置类

4.把set集合放入this.reader.loadBeanDefinitions(configClasses)方法,注册到BeanDefinitionMap中

ConfigurationClassPostProcessor的核心代码在postProcessBeanDefinitionRegistry的processConfigBeanDefinitions中,并且processConfigBeanDefinitions是bean在实例化前调用的(postProcessBeanDefinitionRegistry在bean实例化之前执行)

4.4.1. ConfigurationClassUtils

这里先看看ConfigurationClassUtils类,他的几个核心方法被上面代码所调用,下面讲解一下这几个核心方法,代码如下:

ConfigurationClassUtils.isFullConfigurationClass()

下面两张图对比值来看

ConfigurationClassUtils.isLiteConfigurationClass()

下面两张图对比值来看

关于full和lite的说明

这个配置类有两种情况,一种是传统意义上的带上@Configuration注解的配置类,还有一种是没有

带上@Configuration,但是带有@Component,@Import,@ImportResouce,@Service,

@ComponentScan等注解的配置类,在Spring内部把前者称为Full配置类,把后者称之为Lite配置

类。在本源码分析中,有些地方也把Lite配置类称为普通Bean

ConfigurationClassUtils.checkConfigurationClassCandidate()

首先判断BeanDefinition是扫描注解 还是自己添加的,拿到metadata对象,metadata中包含了类的注解信息。

beanDefinition.getMetadata()这个方法要特别注意,后面代码会频繁出现,metadata获取的是beandefinition的元信息,里面包含了类的注解信息,后面要判断类的注解信息是什么,例如@Import,然后处理import里面内容

根据metadata中的注解判断是full匹配还是lite匹配,然后set 标识符 进去

isFullConfigurationCandidate()方法就是判断是不是@Configuration注解

isLiteConfigurationCandidate()方法就是判断是不是Component,ComponentScan Import ImportResource和@bean 注解

这个代码是不是很熟悉,就是前面CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))的时候获取为空,在这里才正式set进去

4.4.2. 进入ConfigurationClassParser类.parse(Set<BeanDefinitionHolder>)

4.4.2.1. 进入parse(AnnotationMetadata , String)方法

注:这个跟上面的方法都是属于ConfigurationClassParser类

这里把配置类的元信息和配置类的beanName封装成ConfigurationClass对象,这个对象在后面经常用到,这个对象也代表着一个配置类,例如被@Import、@Component注解的具体的配置类

4.4.2.2. 进入processConfigurationClass()

注:这个跟上面的方法都是属于ConfigurationClassParser类

4.4.2.2.1. configurationClasses.put()

这个集合存放的是被解析的配置类的集合,后面要返回这个集合,用于注册bean

4.4.2.3. 进入doProcessConfigurationClass()

注:这个跟上面的方法都是属于ConfigurationClassParser类

下面代码流程图:

这个doProcessConfigurationClass()是专门解析配置类的,这里面主要是五段逻辑

1.解析@PropertySource注解,加载properties文件创建PropertySource对象加入到Environment对象中

2.扫描@ComponentScan注解的basePackages属性的路径,并把该路径下的所有配置类解析和注册进ioc容器中

3.处理 @Import注解里的类,例如@Import(TestClass.class),那就将TestClass配置类放到processConfigurationClass递归循环处理

4.处理 @ImportResource,将locations指向的xml文件变成resources,并存到ConfigurationClass类的importedResources字段中,待后面注册

5.处理 @Bean methods,并存到ConfigurationClass类的beanMethods字段中,待后面注册

我们主要看的是第三段逻辑,调用processImports()方法

4.4.2.4. 进入processImports()

注:这个跟上面的方法都是属于ConfigurationClassParser类

这里专门解析@Import注解

这里有三段核心的if-else逻辑

1.第一段处理实现了ImportSelector接口的,如果是子类deferredImportSelectors,那就先存到集合中,后面再处理,如果不是子类,就递归解析-- 直到成普通组件,就会走到第三段逻辑

2.第一段处理实现了ImportBeanDefinitionRegistrar接口,实现ImportBeanDefinitionRegistrar接口的bean,放到类变量configurationClasses里面去了,则调用了configurationClasses.addImportBeanDefinitionRegistrar()方法

3.处理的是没有实现ImportSelector和ImportBeanDefinitionRegistrar这些接口的普通bean,以及第一段代码递归解析的普通组件,调用processConfigurationClass()方法处理,而这个方法在前面 4.4.2.2 出现过 ,就是专门处理解析某个配置类的

下面附上这三个ConfigurationClass的属性字段

存@ImportResource解析出来的内容

存@Bean解析出来的内容

存@Import 实现了ImportBeanDefinitionRegistrar接口的内容

4.4.3. processDeferredImportSelectors()

上面已经告一段落了,ConfigurationClass里面的字段已经获取到了相应值,接下来回到 4.4.2 里面的ConfigurationClassParser类.parse(Set<BeanDefinitionHolder>) 方法

4.5. 进入ConfigurationClassPostProcessor类的 reader.loadBeanDefinitions(configClasses)

还记得 4.4 段落的的ConfigurationClassPostProcessor类.processConfigBeanDefinitions()方法吗,上面我们已经讲过了这个方法里面的parser.parse()流程了,这里已经解析了配置类的信息,下面需要调用reader.loadBeanDefinitions()方法去ConfigurationClass类里的字段获取值,并注册相关的beanDefinition了。

ConfigurationClass类里的字段在 4.4段落 有列出来

我们先看一下这个reader

这个reader就是用来读取配置信息的,读取后,会调用他的属性值BeanDefinitionRegistry去注册beanDefinition,看这个名称就知道他是BeanDefinition的注册工具了

下面我们正式进入loadBeanDefinitions()方法

进入ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions()

循环ConfigurationClass类,并调用loadBeanDefinitionsForConfigurationClass()

看到这里就很清晰了,这个方法就是把ConfigurationClass类里面的属性值分类好,然后去“对号入座”,看是拥有什么注解,并注册beanDefinition,我们进入其中一个registerBeanDefinitionForImportedConfigurationClass()方法看看

进入registerBeanDefinitionForImportedConfigurationClass()

进入DefaultListableBeanFactory

最后:作为图灵学院一份子,学习来源于周瑜和徐庶两位我特别敬重的老师,站在巨人的肩膀上增加了自己的理解,本文不以商业化为目的,纯个人学习使用

到这里就结束了!!!

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序源仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值