循环依赖就是我依赖你、你依赖我,或者A依赖B、B依赖C、C依赖A…组成的错综复杂的依赖关系。
其实各种不同的依赖关系最终从逻辑上都可以演变为:我依赖你、你依赖我。
循环依赖大致可以分为两种情况:
- 属性依赖:比如A对象有一个属性B,B对象有属性A。
- 构造器依赖:A对象的构造器中有参数B,B对象的构造器有参数A。
构造器依赖是Spring解决不了的(指的是A、B不存在其他构造器,或者Spring需要通过互相依赖的构造器创建A、B对象),这种死结你自己写代码都没办法解决,Spring当然也没有办法。
Spring通过其特有的三级缓存机制能解决属性依赖。顺便说一下,其实Spring官网或相关资料中并没有提出过三级缓存的说法,也不太清楚这种说法从哪儿来(或者是我疏漏了没有查到),反正很NB、很高深的样子,其实如果你认真读源码,换做一种真实的说法:Spring最终通过三个Map的巧妙使用解决循环依赖问题…也就没有那么NB、那么高深、尤其是没有那么高大上了。但是不管怎么说,Spring解决循环依赖的框架还是值得我们认真学习掌握的。因为Spring各路大牛的设计思想确实#@¥%……%*(确实和你的收入会直接相关所以你一定有必要花点时间精力彻底搞清楚)。
三级缓存框架
先上一张图,对三级缓存有个直观印象:
抱歉,画的有点low,不过还是能说明问题的哈。
Spring IoC容器中当然不止是DefaultSingletonBeanRegistry一个容器,但三级缓存就是针对单例Bena来说的,其实如果只是从Bean容器的角度来讲,Spring IoC容器很大程度上讲的也就是单例Bean的容器,原型Bean本来也是每次使用、每次创建,所以也就不需要缓存。只有单例Bean在Spring IoC容器中是一次创建长期驻留,驻留的地方就是三级缓存中的“一级缓存”。
如上图,三级缓存定义在DefaultSingletonBeanRegistry中:
- 三级缓存:存储bean name和bean工厂的容器。
- 二级缓存:存储bean name和bean实例,二级缓存中的bean实例刚刚完成实例化、尚未完成属性赋值,所以是半成品。
- 一级缓存:存储最终完成创建的Bean,应用getBean或者自动装配的Bean就是从以及缓存获取的。
Spring解决循环依赖的详细过程
其实认真读源码,研究完Spring的Bean创建过程之后,循环依赖的原理就一目了然了(虽然很复杂)。
Spring的Bean创建过程相对来说还是比较复杂的,研究清楚全貌还是要花费点时间的,我们的策略就是一步一步抽丝剥茧以及各个击破,今天的主要目标就是三级缓存解决循环依赖的过程,所以对其他过程就要忽略。
Spring源码,Bean的创建过程:
AbstractBeanFactory#getBean
创建和获取bean的统一入口,会调用doGetBean方法。
AbstractBeanFactory#doGetBean
- 首先调用DefaultSingletonBeanRegistry#getSingleton(beanName,true)方法,注意这个getSingleton方法是不会创建bean的。
- 如果没有拿到Bean,就准备创建Bean
- 首先检查是否有Dependon,有的话,先创建Dependon
- 如果要创建的是单例bean,则调用getSingleton(beanName,factory)创建bean,注意这个才会创建bean
- 否则,如果是原型bean…和今天的话题无关,暂时忽略
DefaultSingletonBeanRegistry#getSingleton(beanName,boolean)
我们上面说过了,这里不会创建bean:
- 一级缓存存在,则直接返回
- 一级缓存不存在并且Bean正在创建中,从二级缓存获取
- 二级缓存获取到则返回,否则检查三级缓存
- 三级缓存有的话,调用三级缓存的bean工厂加工bean,放入二级缓存后,返回,并清除三级缓存
- 否则三级缓存没有的话返回null
DefaultSingletonBeanRegistry#getSingleton(beanName,Factory)
这个getSingleton担负着创建bean实例的任务,如果获取不到的话,会创建:
- 一级缓存存在,则直接返回
- 否则,当前bean放入“正在创建中”列表(singletonsCurrentlyInCreation)
- 使用Factory创建Bean实例:调用AbstractAutowireCapableBeanFactory#createBean方法
- 当前bean从“正在创建中”列表移出
- 完成创建的Bean从二级、三级缓存移出,放入一级缓存
我们需要注意的是,这个方法调用完成之后,一个完整的bean就被创建出来、被放入到一级缓存了。
但你要知道的是,这个方法不会那么容易完成的,玄机就在createBean方法中,如果有依赖的话还会递归调用AbstractBeanFactory#getBean方法的,逻辑又绕回去了…
AbstractAutowireCapableBeanFactory#createBean
真正创建Bean的地方:
- 创建Bean实例(原始类的实例,或者CGLIB代理对象实例)
- 如果是单例Bean并且当前Bean正在创建中,则调用addSingletonFactory:作用是创建一个工厂方法,添加到三级缓存。
- 调用populateBean方法,属性填充(可能会发生循环依赖啊…)
需要特别注意的是,三级缓存就是在这儿创建的,存储的内容是一个bean factory、使用lamda表达式创建,工厂方法是getEarlyBeanReference。
getEarlyBeanReference方法后面从三级缓存获取对象的时候会回调,作用是通过SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法最终调用到wrapIfNecessary方法,目的是判断bean如果需要AOP的话则生成他的代理对象。
三级缓存存储bean factory、而不是存储bean实例的目的就是为了解决依赖注入过程中的代理对象的处理的。
这部分内容确实有点绕,是需要动动脑子认真仔细思考一下才能搞明白的。
AbstractAutowireCapableBeanFactory#populateBean
属性填充,这个过程中处理自动封装,会有依赖注入。
调用AutowiredAnnotationBeanPostProcessor#postProcessProperties方法。
然后,AutowiredAnnotationBeanPostProcessor#postProcessProperties进行属性赋值。
然后,再调用DefaultListableBeanFactory#resolveFieldValue方法。
最后,老套路,DefaultListableBeanFactory#doResolveDependency方法。
方法doResolveDependency中首先解决候选注入对象的问题,这部分代码中包含依赖注入是查找对象(DL)的真相,这部分不是今天的主题,跳过。
DL查找到的肯定不是待注入的对象,而是待注入对象的beanName。
那猜想一下,拿到待注入的beanName之后,一定是要拿到bean对象之后再注入的,Spring会怎么处理呢?怎么根据beanName拿到bean对象呢?
其实不难猜到!
doResolveDependency方法会调用DependencyDescriptor#resolveCandidate方法,resolveCandidate方法调用beanFactory.getBean获取对象,其实最终调用的是AbstractBeanFactory的getBean方法。
揉一揉昏花的老眼,是否感觉到似曾相识呢?
没错,往上翻,就是我们开始分析循环依赖的入口处,又转回去了。
所以没错,Spring IoC在Bean实例化、依赖注入的过程,就是一个不断递归调用的过程。
小结
以上,就是Spring IoC的bean的实例化以及依赖注入、三级缓存解决循环依赖的主要逻辑了,整理一下、用几个例子推导一下,相信就能搞清楚这部分内容了。
你如果不相信的话我们下一篇文章就来推导一下。文章来源:https://www.toymoban.com/news/detail-500531.html
上一篇 Spring FrameWork从入门到NB -classpath扫描和组件托管文章来源地址https://www.toymoban.com/news/detail-500531.html
到了这里,关于Spring FrameWork从入门到NB -三级缓存解决循环依赖内幕 (一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!