zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

Spring源码学习笔记7——Spring bean的初始化

2023-04-18 12:32:58 时间

一丶前言

上篇中我们了解了Spring bean的实例化——存在方法覆盖的使用CGLIB动态代理生成子类,反之反射调用构造函数。实例化后bean中的字段都是默认值,接下来就是对bean的属性进行填充,并且还会调用一些生命周期相关的方法

二丶源码学习的简单例子

  • image-20220604125831561

  • 基于xml配置

    image-20220604112426636

三丶属性注入

image-20220604113012335

属性注入的操作集中再populateBean方法中

1.前置检查

image-20220604113533309

如果没有需要注入的值那么直接跳过,mybatis中的mapper一般情况下都会跳过,

  • xml配置和PropertyValues举例

    image-20220604114759407

2.InstantiationAwareBeanPostProcessors # postProcessAfterInstantiation

调用所有InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法,InstantiationAwareBeanPostProcessor实例化感知接口,postProcessAfterInstantiation入参有bean对象和bean的名称,可以在这里面进行自定义的属性注入,如果返回false表示后续不需要spring帮我们进行依赖注入,反之需要spring帮我们进行依赖注入

3.属性注入

3.1applyPropertyValues

属性注入发生在applyPropertyValues方法中

Spring构建一个BeanDefinitionValueResolver来解析属性,BeanDefinitionValueResolver持有当前BeanFactory,bean的定义和类型转换器,在其resolveValueIfNecessary方法中定义了许多不同类型的解析方法

image-20220604121643647

  • 解析RuntimeBeanReference类型

    image-20220604121755855

    • 根据类型解析

      如果存在多个符合要求的bean(bean必须是依赖注入的候选者,且类型符合),根据符合要求bean的Primary信息觉得使用哪个bean,如果还是无法判断,那么根据优先级选择,后续回到父BeanFactory(要求父BeanFactory是AutowireCapableBeanFactory类型)中找符合要求的bean

    • 根据名称解析bean

      会先对bean名称进行spel的解析,然后从工厂中找对应名称的bean,当然也会去父工厂中找

    • 注册依赖关系

      beanA需要beanB进行属性注入,那么会

  • 解析TypeStringValue类型

    解析spel表达式的值,并且进行类型转换

  • 反射设置属性

    image-20220604124713057

3.2 @Resource,@Autowired,@Value注解是如何生效的

image-20220604125835667

这里需要了解两个接口

MergedBeanDefinitionPostProcessor和 InstantiationAwareBeanPostProcessor

@Resource,@Autowired,@Value的实现依赖于这两个接口,首先在MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition,会去扫描class字段or方法上面的注解保存在map缓存中,后InstantiationAwareBeanPostProcessor#postProcessPropertyValues在和依赖之前的扫描到结果进行依赖注入

实现了MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法,此方法在实例化bean之后会回调,image-20220604143116179

InstantiationAwareBeanPostProcessor#postProcessProperties在populateBean方法中会遍历所有的InstantiationAwareBeanPostProcessor方法,调用postProcessProperties方法从而进行依赖注入

3.2.1@Resouce注解实现的原理

这两个注解发挥作用依赖于 CommonAnnotationBeanPostProcessor,

image-20220605113622859

CommonAnnotationBeanPostProcessor在这个方法中会反射找该类中@Resouce注解标注的字段or方法

image-20220604143419211

image-20220604143754276

  • 标注@Resouce注解的字段

    image-20220604144534462

    会获取类中定义的字段,如果是静态字段抛出异常,如果是需要忽略的类型那么不做处理(ignoredResourceTypes 可以进行配置)

  • 标注@Resouce的方法

    image-20220604144931174

以上两步都会循环处理父类,直到父类是Object
  • ResourceElement的构造方法

    • @Resource注解name处理

      如果字段上的注解没有指定name,默认使用字段名称作为资源名称,如果是方法上面没有标注注解,如果是一个set方法(setABC),那么资源名称是aBC,如果不是set方法那么资源名称是方法名称

    • 支持占位符的解析

    • 资源类型@Resouce的type

      如果指定了type 会确认字段类型 or方法第一个参数类型是否和指定类型匹配,指定类型必须是字段or方法第一个参数类型的子类或者就是字段or方法第一个参数的类型,如果没有指定类型,那么类型是字段对应的类型,or方法第一个参数的类型

    • 还支持搭配Lazy注解实现懒注入?

  • 依赖注入

    如果是字段那么反射根据设置字段值,如果是方法反射调用方法,其中getResouceToInject会从容器中获取需要的bean

    image-20220604155532024

    image-20220604151519104

这里有个细节的点,很多博客上面都说

image-20220604153316458

我本人尝试却不是这样的,其实这里根本没有根据类型,根据名称。如下例子

image-20220604154256282

但是这样下面这样又是可以成功注入的

image-20220604154647035

为什么昵

image-20220604155039259

也就是说如果我们没有指定@Resouce的name,且字段的名称,or set方法去除set后第一个字母小写,非set方法就是方法名称,在BeanFactory中不存在那么会根据类型去找,

如果指定了name,或者包含且字段的名称,or set方法去除set后第一个字母小写,非set方法就是方法名称在BeanFactory中存在,会按照名称找

3.2.1.@Autowired 和 @Value 注解的原理

这两个注解功能的实现依赖于AutowiredAnnotationBeanPostProcessor,大流程和CommonAnnotationBeanPostProcessor是一样的

image-20220605131724561

3.2.1.1@Autowired

原理和@Resouce如出一辙

  • postProcessMergedBeanDefinition方法扫描注解保存到map缓存

    image-20220605114416856

    具体扫描逻辑在buildAutowiringMetadata方法中

    1. 扫描字段

      image-20220605114804771

    2. 扫描方法

      image-20220605115213039

  • 依赖注入

    • 字段注入

    image-20220605122435870

    最终调用的是BeanFactory的resolveDependency方法,然后调用doResolveDependency方法,后续找候选的bean调用findAutowireCandidates

    image-20220605123747008

    image-20220605125028408

    如果存在多个符合要求的bean候选者,通过determineAutowireCandidate方法,@primary标注的bean将胜出,如果都没有标注,那么比较优先级javax.annotation.Priority中的值,如果还是不行再根据字段名称or方法参数名称

    • 方法注入

      image-20220605122557991

      找bean候选者的过程和字段注入相同,但是和@Resouce不同的是,@Autowird支持多个参数和0个参数,多个参数都会调用resolveDependency方法找合适的候选者,0个参数还是会反射调用方法,入参是空数组

3.2.1.2 @Value

@value注解的流程和@Autowired是一样的,但是在doResolveDependency方法的时候

ContextAnnotationAutowireCandidateResolver #getSuggestedValue 会解析Value注解给出建议的值

image-20220605131544242

3.3@Qualifier如何生效

在resolveDependency调用doResolveDependency方法,然后调用 findAutowireCandidates 其中会调用isAutowireCandidate判断当前的bean是否是候选bean,会使用QualifierAnnotationAutowireCandidateResolver中的isAutowireCandidate方法image-20220605142114946

来判断是否是合格的候选者,可以看到在方法注入的时候,也可以标注在参数上来指定bean名称

3.4总结@Resouce @Autowried的异同

  1. 相同

    1. 都可以标注在方法上和字段上
    2. 都可以使用@Qualifier 限定候选者
    3. 都可以进行依赖注入
    4. 都是通过 MergedBeanDefinitionPostProcessor和InstantiationAwareBeanPostProcessor的回调实现依赖注入的
    5. 底层原理都是反射,反射设置字段的值,反射调用方法
    6. 二者都是在实例化bean之后,调用MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法扫描需要注入的字段 和 方法 放入到缓存中,后
    7. 依赖注入都是发生在InstantiationAwareBeanPostProcessor#postProcessPropertyValues的方法中
    8. 依赖注入存在多个符合条件的bean都是通过@Primary 的bean胜出(实际上调用BeanDefinition中的isPrimary()),如果都isPrimary 不能决出胜负则比较优先级,如果还是存在多个那么抛出异常
    9. 二者都在找不到合适的bean的时候抛出异常
  2. 不同

    1. @Resouce 是基于CommonAnnotationBeanPostProcessor 实现的

    2. @Autowried 是基于AutowiredAnnotationBeanPostProcessor 实现的

    3. @Resouce注解默认是根据bean的名称去注入,如果没有指定name,会将字段的名称作为bean的名称,set方法会去掉set前缀且第一个字母小写作为bean的名称,非set方法 方法名称就是bean的名称,如果指定了name 就以name中的内容作为bean的名称

      如果没有指定name,使用默认的name,且默认的name在容器中存在,那么调用resolveByBeanName获取bean,要求类型必须兼容字段声明类型or方法入参类型

      如果没有指定name且name不存在容器中,会根据类型注入

    4. @Autowried 默认根据类型注入,比如Service存在实现类A和B,字段or方法声明类型为AService但是字段or参数名称为b,还是会根据类型找到符合的bean名称找到a,而不会因为名称是b选择B注入,如果字段or方法参数类型是Service,但是字段or参数名称为a,且没有标注@Qualifier 那么会找到a和b作为候选者,但是后面比较Primary,再比较javax.annotation.Priority注解,然后再根据字段的名称or参数的名称去筛选

    5. @Autowried 标注在方法上支持 0个参数 和任意多个参数 但是@Resouce 要求必须有一个参数

@Resouce 优先根据bean名称注入的前提是 指定了name or 没有指定name但是字段名称,set方法去除set前缀第一个字母小写的名称or方法名称的对应的bean 存在于容器中
如果不满足根据名称注入的前提,也就是没有指定name,名称指示的bean不在容器中,会根据类型来注入,这个时候逻辑就和@Autowried  一样

那么根据bean的类型注入存在多个bean的时候判断的优先级是什么(@Resouce or @Autowried根据类型注入都是这个优先级)
1.BeanDefinition的isPrimary 返回true (一般是通过注解orxml 配置设置为true)
2.比较优先级,注意这里的优先级不是@Order,Ordered,@PriorityOrdered可以左右的,而是要javax.annotation.Priority 注解才能左右
3.根据字段名称,or方法参数名称 来决定使用哪个bean

@Qualifier 是在根据类型筛选Bean的得到多个bean之后调用 isAutowireCandidate 中判断候选者的名称是否符合的时候生效的,是优先于根据字段名称,or方法参数名称 的,根据字段名称,or方法参数名称 是最后无法决定才会使用的策略,@Qualifier是找符合类型的bean后进行的过滤

四丶初始化bean

image-20220605151419819

在populateBean 完成bean的依赖注入之后,会进行bean的初始化

image-20220605151800877

一共有四步,回调Aware接口,回调 BeanPostProcessor 的postProcessBeforeInitialization方法,执行初始化方法,回调BeanPostProcessor的postProcessAfterInitialization方法

1.回调Aware接口

image-20220605151938757

你可能会想那ApplicationContextAware昵,EnvironmentAware昵,拜托,这是在BeanFactory里面耶,没什么要调用和BeanFactory 无关的接口啊,所有它们何时被调用,请接着看下去

2.回调 BeanPostProcessor 的postProcessBeforeInitialization

image-20220605152219168

2.1 ApplicationContextAwareProcessor

这个类负责回调一些Aware接口(和BeanFactory无关的Aware)

image-20220605152358856

image-20220605152415475

2.2InitDestroyAnnotationBeanPostProcessor

这个类负责实现@PostConstruct和@PreDestroy 注解标注的方法,这个类被CommonAnnotationBeanPostProcessor 实现

image-20220605153453895

具体原理和@Resouce一样,首先都是postProcessMergedBeanDefinition方法扫描注解(bean实例化后该方法被回调)

image-20220605153603166

找生命周期相关的注解

image-20220605153825953

然后再postProcessBeforeInitialization 回调的时候会执行相关的@PostConstruct标注的方法

image-20220605154053526

注意那怕方法需要参数,spring 也不会管,直接参数就是null,

同理bean被销毁的时候回调对应的方法postProcessBeforeDestruction 进行@PreDestory标注方法的回调

2.3ServletContextAwareProcessor

image-20220605154953441

负责回调ServletContextAware 和 ServletConfigAware 类型对象对应的方法

3.执行初始化方法

image-20220605152925172

这里的用户自定义初始化方法 可以通过@Bean(initMethod = "initMethod") 和xml配置中的Init-method指定,具体逻辑就是反射拿到方法,然后反射执行

4.回调BeanPostProcessor的postProcessAfterInitialization

image-20220605154308142

这里是Aop 实现的重点,可以在这里返回代理对象实现动态代理,最终是动态代理的对象放入到了容器中, 后续会进行详细的学习

4.1ApplicationListenerDetector

这里实现了监听器的注册 ApplicationListenerDetector 会判断如果当前bean实现了ApplicationListener 那么会进行注册,这个之前笔记讲到过

image-20220605154519410