Spring 源码分析之 Spring 三级缓存解决循环依赖问题
2019-08-24 本文已影响0人
七月_JulyFY
1,什么是 Spring 的循环依赖
简单来讲,就是有一个 A 对象,创建 A 的时候发现 A 对象依赖 B,然后去创建 B 对象的时候,又发现 B 对象依赖 C,然后去创建 C 对象的时候,又发现 C 对象依赖 A。这就是所谓的循环依赖。
![](https://img.haomeiwen.com/i15495530/d3e4dfd6284d77fe.png)
那么 Spring 在创建 Bean 的时候是如何解决这种关系的依赖呢?(循环依赖)先抛出结论,Spring 使用了三级缓存解决了循环依赖的问题。并且 Spring 能解决哪些循环依赖不能解决哪些循环依赖。以下会详细的阐述。
2,什么是三级缓存
1,第一级缓存:单例缓存池 singletonObjects。
2,第二级缓存:早期提前暴露的对象缓存 earlySingletonObjects。
3,第三级缓存:singletonFactories 单例对象工厂缓存
3,什么是早期暴露的对象
所谓的早提提前暴露的对象就是说,你是一个不完整的对象,你的属性还没有值,你的对象也没有被初始化。这就是早期暴露的对象,只是提前拿出来给你认识认识。但他非常重要。这是多级缓存解决循环依赖问题的一个巧妙的地方。
4,创建 Bean 的整个过程
getSingleton 方法详解
1,getBean 方法肯定不陌生,必经之路,然后调用 doGetBean,进来以后首先会执行 transformedBeanName 找别名,看你的 Bean 上面是否起了别名。然后进行很重要的一步,getSingleton,这段代码就是从你的单例缓存池中获取 Bean 的实例。那么你第一次进来肯定是没有的,缓存里肯定是拿不到的。也就是一级缓存里是没有的。那么它怎么办呢?他会尝试去二级缓存中去拿,但是去二级缓存中拿并不是无条件的,首先要判断 isSingletonCurrentlyInCreation(beanName) 他要看你这个对象是否正在创建当中,如果不是直接就退出该方法,如果是的话,他就会去二级缓存 earlySingletonObjects 里面取,如果没拿到,它还接着判断 allowEarlyReference 这个东西是否为 true。它的意思是说,是否允许让你从单例工厂对象缓存中去拿对象。默认为 true。好了,此时如果进来那么就会通过 singletonFactory.getObject();去单例工厂缓存中去拿。然后将缓存级别提升至二级缓存也就早期暴露的缓存。然后,如果说你第一次都没有从单例缓存中没有获取到对象并且 isSingletonCurrentlyInCreation 也没有被标记。那么直接就返回了后面的流程都不走。
dependsOn
2,getSingleton 执行完以后会走 dependsOn 方法,判断是否有 dependsOn 标记的循环引用,有的话直接卡死,抛出异常。比如说 A 依赖于 B,B 依赖于 A 通过 dependsOn 注解去指定。此时执行到这里就会抛出异常。这里所指并非是构造函数的循环依赖.
beforeSingletonCreation
3,在这里方法里。就把你的对象标记为了早期暴露的对象。提前暴露对象用于创建 Bean 的实例
createBean
4,紧接着就走创建 Bean 的流程开始。在创建 Bean 之前执行了一下 resolveBeforeInstantiation 这个东东。它的意思是说,代理 AOPBean 定义注册信息(也就是那个蛋)但是这里并不是实际去代理你的对象,因为你只是个蛋。你还没有被创建,我给你代理个锤子啊?只是代理了你的 Bean 定义信息,还没有被实例化。把你的 Bean 定义信息放进缓存,以便我想代理真正的目标对象的时候,直接去缓存里去拿。
doCreateBean
5,接下来就真正的走你的创建 Bean 流程
首先走进真正做事儿的方法 doCreateBean 然后找到 createBeanInstance 这个方法,在这里面它将为你创建你的 Bean 实例信息(Bean 的实例)。如果说创建成功了,那么就把你的对象放入缓存中去(将创建好的提前曝光的对象放入 singletonFactories 三级缓存中)将对象从二级缓存中移除因为它已经不是提前暴露的对象了。但是。如果说在 createBeanInstance 这个方法中在创建 Bean 的时候它会去检测你的依赖关系,会去检测你的构造器。然后,如果说它在创建 A 对象的时候,发现了构造器里依赖了 B,然后它又会重新走 getBean 的这个流程,当在走到这里的时候,又发现依赖了 A 此时就会抛出异常。为什么会抛出异常,因为,走 getBean 的时候他会去从你的单例缓存池中去拿,因为你这里的 Bean 还没有被创建好。自然不会被放进缓存中,所以它是在缓存中拿不到 B 对象的。反过来也是拿不到 A 对象的。造成了死循环故此直接抛异常。这就是为什么 Spring IOC 不能解决构造器循环依赖的原因。因为你还没来的急放入缓存你的对象是不存在的。所以不能创建。同理 @Bean 标注的循环依赖方法也是不能解决的,跟这个同理。那么多例就更不能解决了。为什么?因为在走 createBeanInstance 的时候,会判断是否是单例的 Bean 定义信息 mbd.isSingleton();如果是才会进来。所以多例的 Bean 压根就不会走进来,而是走了另一段逻辑,这里不做介绍。至此,构造器循环依赖和 @Bean 的循环依赖还有多例 Bean 的循环依赖为什么不能解决已经解释清楚。然后如果说,Bean 创建成功了。那么会走后面的逻辑
![](https://img.haomeiwen.com/i15495530/cb390d034fec7b5a.png)
6,将创建好的 Bean 放入缓存
addSingletonFactory。方法就是将你创建好的 Bean 放入三级缓存中。并且移除早期暴露的对象。
![](https://img.haomeiwen.com/i15495530/b9909bcfa5ae2f8e.png)
populateBean
7,通过 populateBean 给属性赋值
我们知道,创建好的对象,并不是一个完整的对象,里面的属性还没有被赋值。所以这个方法就是为创建好的 Bean 为它的属性赋值。并且调用了我们实现的的 XXXAware 接口进行回调初始化,。然后调用我们实现的 Bean 的后置处理器,给我们最后一次机会去修改 Bean 的属性。其他的地方我不做过多的介绍,我只讲一个重点就是,在 populateBean 里面他会解析你的属性,并且赋值,当发现,A 对象里面依赖了 B,此时又会走 getBean 方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对 createBeanInstance 对象创建完成以后已经放入了缓存当中,所以创建 B 的时候发现依赖 A,直接就从缓存中去拿,此时 B 创建完,A 也创建完,一共执行了 4 次。至此 Bean 的创建完成,最后将创建好的 Bean 放入单例缓存池中 addSingleton();至此 Ioc 创建 Bean 的整个生命周期已经介绍完毕。以及 Spring 是如何通过三级缓存去解决循环依赖的问题。下面附加一张流程脑图。
![](https://img.haomeiwen.com/i15495530/05615b30bf94d28f.png)