Spring循环依赖

写在前面:个人认为循环依赖应该从代码设计层面解决,从根本上杜绝循环依赖,这是个不应该出现的现象,现在的springboot已经默认不支持循环依赖了。

一、什么是循环依赖

多个bean之间相互依赖,形成了一个闭环。 比如:创建A时,发现 A 要依赖 B,发现 B 还没创建。于是开始创建 B ,创建的过程发现 B 要依赖 A, 而 A 还没创建好呀,因为它要等 B 创建好,形成死锁的感觉。

二、Spring 如何解决循环依赖

解决方式:提前暴露未完全创建完的 Bean

1、前提
解决循环依赖有两个前提:

(1)、依赖的bean都是单例
对于“prototype”作用域Bean,每次实例化都是一个新的对象,创建 A1 需要创建一个 B1。创建 B1 的时候要创建一个 A2。创建 A2 又要创建一个 B2。创建 B2 又要创建一个 A3。。。永无止境

(2)、必须不全是构造器注入
构造器注入依赖的对象必须是完全创建的对象,不满足提前暴露的方式

(3)、不使用@Async(或者加@Lazy注解解决)
@Async注解是初始化之后再去生成代理,@Async注解起作用是靠AsyncAnnotationBeanPostProcessor这个类实现的,这个类会处理@Async注解。

早期暴露出去的代理对象是通过AnnotationAwareAspectJAutoProxyCreator这个类的getEarlyBeanReference方法获取的。

但是AsyncAnnotationBeanPostProcessor并没有实现SmartInstantiationAwareBeanPostProcessor,也就是在获取早期对象这一阶段,并不会调AsyncAnnotationBeanPostProcessor处理@Async注解。

2、spring解决循环依赖的方式
三级缓存:

/** Cache of singleton objects: bean name to bean instance. 一级缓存*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. 三级缓存*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. 二级缓存*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

singletonObject: 一级缓存,该缓存key = beanName, value = bean;这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存
earlySingletonObjects: 二级缓存,该缓存key = beanName, value = bean;这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用
singletonFactories: 三级缓存,该缓存key = beanName, value = beanFactory;在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存。在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。
具体流程如下:

A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B,B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的AB顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A放到一级缓存中。

@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     //先从一级缓存取
        Object singletonObject = this.singletonObjects.get(beanName);
     //一级缓存中没有,且正在创建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
          //从二级缓存取
                singletonObject = this.earlySingletonObjects.get(beanName);
          //二级缓存也没有且允许提前暴露
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
              //获取提前暴露实例
                        singletonObject = singletonFactory.getObject();
              //放入二级缓存
                        this.earlySingletonObjects.put(beanName, singletonObject);
              //从三级缓存删除
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

为什么是三级缓存而不是二级缓存??

在实例化 Bean A 之后,我在二级 map 里面塞入这个 A,然后继续属性注入。

发现 A 依赖 B 所以要创建 Bean B,这时候 B 就能从二级 map 得到 A ,完成 B 的建立之后, A 自然而然能完成。

上述流程其实只需要二级缓存就可以实现,原因有两个:

(1)AOP;在spring生命周期中AOP代理在初始化之后的后置处理器完成,AOP代理后的bean就已经不再是原来的bean了,经过代理后的bean是一个全新的bean,那之前的暴露的对象就是错的。

(2)所有bean如果在放入二级缓存之前就预先执行后置处理器?貌似也行,但这样就违背了spring生命周期的设计,如果是三级缓存就只有存在AOP且循环依赖的bean才会提前执行,尽量不打破原有的生命周期设计。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值