spring源码日记13: spring 循环依赖

作者 : 开心源码 本文共3114个字,预计阅读时间需要8分钟 发布时间: 2022-05-13 共202人阅读

想彻底弄清楚spring的循环依赖问题,首先得弄清楚1. 循环依赖是如何发生的,spring又是如何检测循环依赖的发生的。其次才是2. 探索spring如何处理循环依赖的问题

1. 循环依赖检查

<bean id="a" class="A">    <property name="b" ref="b"><bean/><bean id="b" class="B">    <property name="a" ref="a"><bean/>

无论单例还是原型模式(下文①代表图中步骤1),spring都有对应的集合保存当前正在创立的beanName,标识该beanName正在被创立。在bean创立前,①检测当前bean能否在创立中,假如不在创立中则②将beanName加入集合,往下创立bean。在bean创立前,检测到当前的bean正在创立,则说明发生循环依赖,抛出异常。最后记得当bean创立完时将beanName移出集合。

循环依赖检测.jpg

2. 循环依赖的解决

单例setter循环依赖

spring注入属性的方式有多种,但是只有一种循环依赖能被处理:setter依赖注入。前面或者多或者少都提到了spring处理循环依赖的做法是未等bean创立完就先将实例曝光出去,方便其余bean的引用。同时还提到了三级缓存,最先曝光到第三级缓存singletonFactories中。不知道三级缓存是什么的可以回顾下:spring源码 (十):spring从缓存中获取bean

示例

// 第一种 注解方式public class A {    @Autowired    private B b;}public class B {    @Autowired    private A a;}// ===========================// 第二种 xml配置方式public class A {    private B b;    // getter setter}public class B {    private A a;    // getter setter}<bean id="a" class="A">    <property name="b" ref="b"><bean/><bean id="b" class="B">    <property name="a" ref="a"><bean/>

分析

单例setter循环依赖.jpg

上图我觉得我画的很满意,堪称灵魂画手。其中跟循环依赖检测比照,新增的几个关键节点已经用黄色标识出来,这里有几个重点给大家画一下。

  1. 提前曝光,假如用c语言的说法就是将指针曝光出去,用java就是将引用对象曝光出去。也就是说即使a对象还未创立完成,但是在④实例化过程中new A()动作已经开拓了一块内存空间,只要要将该地址抛出去b即可以引用的到,而不论a后期还会进行初始化等其余操作

  2. 已经理解了提前曝光的作用,而相比而言⑤曝光的时机也非常的重要,该时机发生在④实例化之后,⑥填充与? 初始化之后。spring循环依赖之所以不能处理实例化注入的起因正式由于注入时机在曝光之前所导致

  3. ⑤中写的带a的工厂是什么东西?先来理解一下ObjectFatory

public interface ObjectFactory<T> {    T getObject() throws BeansException;}

就是一个接口,通过重写getObject()方法返回对应的object

// 将该bean提前曝光,具体做法是创立一个ObjectFactory对象,再将对象加入到singletonFactories缓存中addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

让我帮大家改写一下,不然可能看了有点懵逼,以上代码等同于

addSingletonFactory(beanName, new ObjectFactory<Object>() {    @Override    public Object getObject() throws BeansException {        getEarlyBeanReference(beanName, mbd, bean);    } });

但是我们看到,按原计划重写getObject()应该是直接return bean就行了,为什么还有getEarlyBeanReference是什么鬼?
目的就是未了后置解决,给一个在提前曝光时操作bean的机会,具体要怎样操作bean,那就继承SmartInstantiationAwareBeanPostProcessor重写getEarlyBeanReference方法吧。比方你要System.out.print(“啊啊啊啊,我是” + bean + “,我被曝光啦”)也是可以的

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {        Object exposedObject = bean;        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {            for (BeanPostProcessor bp : getBeanPostProcessors()) {                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;                    // 这么一大段就这句话是核心,也就是当bean要进行提前曝光时,                    // 给一个机会,通过重写后置解决器的getEarlyBeanReference方法,来自己设置操作bean                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);                }            }        }        return exposedObject;    }

单例构造器注入循环依赖

上面已经剧透了这个方式是不得行的,起因是依赖注入的时间点不对,他的依赖注入发生在构造器阶段,这个时候连实例都没有,内存都还没开拓完,当然也还没有进行提前曝光,因而不得行

示例

public class A {    private B b;    @Autowired    public A(B b) {        this.b = b;    }}public class B {    private  A a;    @Autowired    public B(A a) {        this.a = a    }}

分析

单例构造器注入循环依赖.jpg

图上重点地方也用黄色标出了,问题的起因处在④实例化,实例化的过程是调用new A(B b);的过程,这是的A还未创立出来,根本是不可能提前曝光的,正是这个起因导致⑨无法获取到三级缓存,进而导致⑩异常的抛出

原型模式循环依赖

这此没有图了,由于原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测,发现原型模式下存在循环依赖并抛出异常

总结

总结一下循环依赖,spring只能处理setter注入单例模式下的循环依赖问题。要想处理循环依赖必需要满足2个条件:

  1. 需要用于提前曝光的缓存
  2. 属性的注入时机必需发生在提前曝光动作之后,不论是填充还是初始化都行,总之不能在实例化,由于提前曝光动作在实例化之后

了解了这2点即可以轻松驾驭循环依赖了。比方构造器注入是不满足第二个条件,曝光时间不对。而原型模式则是缺少了第一个条件,没有提前曝光的缓存供使用

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » spring源码日记13: spring 循环依赖

发表回复