【Spring6源码・IOC】Bean的初始化 - 终结篇
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
前面两篇我们着重讲解了一下《BeanDefinition的加载》和《bean的实例化》。
这一篇我们来讲解一下bean的初始化。
我们这里的案例依旧是以SpringBoot3.0、JDK17为前提案例代码如下
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
首先先明确一下这个三级缓存
一级缓存 singletonObjects 中存放完全初始化好的 bean 的实例。
二级缓存 earlySingletonObjects中存放早期对象未完全初始化完成的 bean 实例。
三级缓存 singletonFactories 中存放 bean 工厂对象。
bean 创建起来比较复杂所以将bean缓存起来方便之后使用。
上一篇我们实例化了bean之后将bean放入了第三级缓存看一下这个addSingletonFactory方法如果一级缓存中没有对应的bean那么会将未初始化的bean放入三级缓存会将bean提前暴露出来。
接下来会通过populateBean()
方法对bean的属性进行填充。
步入populateBean()
方法首先会获取BeanPostProcessors主要是在bean初始化之前做一些事情如下我们总共获取到4个BeanPostProcessors 最后一个AutowiredAnnotationBeanPostProcessor是本节较为关键的BeanPostProcessor它主要是用来处理我们@Autowired注解。
我们进入到AutowiredAnnotationBeanPostProcessor#postProcessProperties方法中首先有个findAutowiringMetadata方法来处理@Autowired
我们步入findAutowiringMetadata方法中发现会从injectionMetadataCache的map集合中获取。
但是我们没有往这个缓存中put对象啊所以这个metadata是null而后我们会简单的判断一下
public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class<?> clazz) {
return (metadata == null || metadata.needsRefresh(clazz));
}
如果为null我们会调用buildAutowiringMetadata方法在这里我们会看到很熟悉的设计就像单例模式一样的双重锁/双重校验锁(DLC即double-checked locking)
而后会进入buildAutowiringMetadata方法去创建相关信息。
这里主要有两点一个是针对属性上的注解一个是针对方法上的注解大部分的时候会放在属性上也有时候会放在方法上比如set方法上。
我们来一起看一下这个处理属性的doWithLocalFields方法首先通过getDeclaredFields方法获取类的属性集合然后进行遍历调用函数去处理。
我们来看一下是如何获取类的属性数组的还用想嘛当然是反射了不信你看。第一次从属性缓存中获取肯定获取不到啊然后反射获取在加入缓存中。
然后包装成AutowiredFieldElement对象add到currElements列表中最后在把各个bean的所有元素add到elements列表中。
最后会通过InjectionMetadata#forElements方法将所有的元素封装成InjectionMetadata对象返回。
最后把注入元素的信息放进injectionMetadataCache的map集合中以便下次就可以从缓存中获取。
到这里我们就解析完了带有@Autowired注解的属性接下来进入我们的核心方法inject。
在这里首先会判断bean中是否有属性如果有循环遍历属性调用inject方法。
属性准备就绪着重看一下resolveFieldValue方法
步入上图的789行
步入doResolveDependency方法最后会对这个属性B进行解析
步入resolveCandidate方法这不是beanFactory么真巧来了老弟。
这不是想去缓存里找么嘿嘿
此时b还没有实例化缓存中肯定找不到啊。
找不到就去创建吧
嗖创建完了。
创建完又要填充属性了B的属性是A来吧在走一圈。
最后还是要去beanFactory中去找因为在a填充属性的时候a就已经实例化过了也已经put进三级缓存暴漏出来了我们来看看是如何处理的。
老样子看看248行代码。
哎卧槽怎么直接跳到下面来了bean工厂里没有那不对啊按理来说应该可以从三级缓存中找到啊。服了看看吧。
经过再一次的debug发现这个allowCircularReferences属性默认是false在SpringBoot2.6.x之前都是默认为true。
如果需要开启循环依赖需要在配置文件配置
spring:
main:
allow-circular-references: true
坑SpringBoot2.6.x默认禁用循环依赖。
开启之后再来一遍吧。
获取a —》先去缓存中取结果都没有—》创建a实例化—》添加进三级缓存beanFactory—》填充属性b—》获取b—》先去缓存中取缓存中也没有—》创建b实例化—》添加进三级缓存beanFactory—》填充属性a—》先去缓存中找这下肯定有了不信我们看看
你看有了吧啥都有了。也能从三级缓存中取到并且把这个bean放进二级缓存earlySingletonObjects中。
然后就会进入如下这个if判断中getObjectForBeanInstance方法中会做一些判断如果是当前实例则会直接返回该实例。
最后得到a退出解析方法
然后将这个属性值放进缓存中
退出该方法
然后把得到的A赋值给B的属性
就此B的属性A赋值结束。此时可以看到B是有值的属性A也是有值的可是A的属性B还没有赋值呢。
此时我们已经创建完b也就是说已经调完了上述的那个lambda函数紧接着会调用addSingleton方法将bean放进一级缓存。
将b放进一级缓存同样也标志着b初始化结束紧接着a会将b设置为自己的属性。最后也会把a放进一级缓存就此over~
当然这里可能还存在很多问题我给的这个demo并不能完全展示出二级缓存的作用因为a只有一个属性b那么填充属性之后a就已经存在一级缓存中。
可以这样A同时依赖于B和CB和C都依赖于A。这样就可以看出二级缓存的作用了。
其实对于解决循环依赖来说两个缓存就完全足够一个存放未初始化的bean一个存放已经初始化的bean那么要三级缓存干什么
具体原因我们可以放在下一篇aop的源码解析。
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |