Spring中的循环依赖解决流程

前言

说起Spring中循环依赖的解决办法,相信很多朋友们都或多或少会提到三级缓存的问题,那么三级缓存到底是怎么解决循环依赖的问题和他的过程是怎么样的,我们使用idea的debug方式,通过跟进源码
来了解一下。

一、什么是三级缓存

一级缓存:singletonObjects,存放完全实例化属性赋值完成的Bean,直接可以使用。

二级缓存:earlySingletonObjects,存放早期Bean的引用,尚未属性装配的Bean三级缓存:singletonFactories。

三级缓存,存放实例化完成的Bean工厂。

二、步骤

1.首先我们先做好debug的准备。
在这里插入图片描述
我们通过这行代码来启动spring。

class A{
    private B b;

    public void setB(B b) {
        this.b = b;
    }

    public A() {
        System.out.println("A  create");
    }
}
class B{
    private A a;
    public void setA(A a) {
        this.a = a;
    }
    public B() {
        System.out.println("B  create");
    }
}

这是两个互相依赖的类A和B

    <bean id="a" class="com.jxau.study.A">
        <property name="b" ref="b"></property>
    </bean>
    <bean id="b" class="com.jxau.study.B">
        <property name="a" ref="a"></property>
    </bean>

spring的配置文件中注入这两个bean。

2.debug

相信大家都知道,spring主要流程都在他的refresh方法中,这里我们直接进入。
在这里插入图片描述
其他方法和循环依赖没有太大关系,这里不考虑,我们直接进入finishBeanFactoryInitialization方法中。
在这里插入图片描述
这里,我们也是直接进入preInstantiateSingletons()方法中。进入到这个方法中后。
在这里插入图片描述
我们可以看到,此时的beanName是 a ,在preInstantiateSingletons方法中,我们通过他的getBean方法进入到deGetBean方法中。
在这里插入图片描述
这里有一个比较重要的方法,this.getSingleton(beanName),F7进去。
在这里插入图片描述
这就是这个方法的全貌,代码并不是很多,但是比较重要,首先去singletonObjects中根据beanName获取,此时因为我们没有对singletonObjects做任何操作,所以此时获取不到,为null,然后判断这个beanName的对象是否正在创建过程中,很明显,我们当前显然没有创建,所以直接返回到我们的doGetBean方法中。我们接着debug。在这里插入图片描述
当我们走到这里,就是一个比较重要的步骤了。此时要解释一下这个getSingleton方法,他是一个函数式接口,他只有一个方法,就是getObject方法,也就是说,他只有在调用getObject方法的时候,才会调用这个lambda表达式,也就是createBean方法。然后我们直接进入到getSingleton方法中。
在这里插入图片描述
值得注意的是, this.beforeSingletonCreation(beanName);这句代码。

protected void beforeSingletonCreation(String beanName) {
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }

它的源码是这样的,他有!this.singletonsCurrentlyInCreation.add(beanName)这么一句代码,也就是说在这个时候,将beanName加入到了singletonsCurrentlyInCreation,这个方法结束后,说明对象开始创建,这个singletonsCurrentlyInCreation集合在以后会用到。在执行完这个方法后,我们看到它会调用singletonObject = singletonFactory.getObject();也就是createBean方法。
在这里插入图片描述
进入到这个方法后,经过一系列的判断,然后会进入到一个叫做doCreateBean的方法,这个方法才会真正的创建对象,我们直接进去。
在这里插入图片描述
当执行完createBeanInstance这个方法后,其实就已经创建好一个对象了,内部其实就是利用反射创建,这里不再多说。这里的bean其实就是A对象,A@1613。我们继续走,这里有这样一个方法。
在这里插入图片描述

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized(this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }

        }
    }

它内部是这个样子,这里就又会涉及到三级缓存了,首先判断singletonObjects,也就是一级缓存是否包含beanName,这里肯定是不包含的,然后他就会向singletonFactories,也就是三级缓存中存入这样一个key,value,key自然是beanName,那么value是什么呢,结合上面我们可以看到又是一个lambda表达式,当然,也是在singletonFactory调用getObject方法的时候调用这个表达式。此时只是在三级缓存中保存起来,并没有调用。
在这里插入图片描述
此时就是这样,至于二级缓存和一级缓存现在并没有对象,所以直接跳过,结束这个方法。
在这里插入图片描述
下一步,走到了populateBean方法,这个方法就是用来填充属性的,我们直接进去。直接走到这个方法的最后一步。
在这里插入图片描述
再点进去。这个方法里呢,会有一个循环,就是用来填充属性的,
在这里插入图片描述
这里我们就可以很清楚的看到,属性名是b,属性值呢就是B对象。
在这里插入图片描述
而且他的类型其实是RuntimeBeanReference,运行时的bean引用。然后我们继续走, 走到这个方法Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);然后进去。
在这里插入图片描述
第一步就是判断value的类型,上面提到,value的类型就是RuntimeBeanReference,所以执行里面的方法,resolveReference方法。直接进去。
在这里插入图片描述
这个方法理,我们又看到了getBean方法,resolvedName自然就是 " b ",在进去。进入到doGetBean方法之后。继续Object sharedInstance = this.getSingleton(beanName);。
在这里插入图片描述
先去一级缓存中找,当然找不到,而且此时b也不再创建中,所以直接返回。
在这里插入图片描述
再一次执行到这里,与上次不同的是,上次是a,这次是b。进去之后,依然会调用getObject方法。也就是createBean去创建B对象。然后再进入doCreateBean中。
在这里插入图片描述
这个方法之后,对象被创建。但依然没有被属性赋值。
在这里插入图片描述
然后走到这个方法。
在这里插入图片描述
和A相同,向三级缓存中存放数据。现在的情况就是这个样子。
在这里插入图片描述
返回之后,在此执行到populateBean方法,去给b对象填充属性。我们依然按照上面的步骤进入populateBean的applyPropertyValues方法中。
在这里插入图片描述
依然是这个循环,可以看到,这一次的属性是a。上一次来到这里,是因为给a对象填充b属性,而因为那时候没有b对象,所以我们就会去创建b对象,创建好对象后,要给这个b对象填充a属性,才会来到这一步。当然,此时的originalValue的类型依然是RuntimeBeanReference。然后下一步,我们再进入到resolveValueIfNecessary方法中。和上面的步骤一样,进入之后会判断类型,依然是RuntimeBeanReference,然后进入resolveReference方法。这里再一次去getBean,doGetBean。
在这里插入图片描述
这里需要留意下一getSingleton方法了。
在这里插入图片描述
还是老样子,去一级缓存中找 a ,显然是找不到的,而此时a也刚好处于一个创建的过程中。那么就会进入到这个判断,再去二级缓存中找,很显然,也是找不到的,还记得我们上面的图吗,这个时候其实是只有三级缓存中有我们想要的 a 和 b 。找到之后,会调用getObject方法,也就是lambda表达式。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
            Iterator var5 = this.getBeanPostProcessors().iterator();

            while(var5.hasNext()) {
                BeanPostProcessor bp = (BeanPostProcessor)var5.next();
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }

        return exposedObject;
    }

就是这个方法。此时的bean其实是a对象,他会直接返回。此时的singletonObject就是A对象。然后二级缓存中添加数据。最后删除掉三级缓存中的a属性。
在这里插入图片描述
此时缓存中就是这样的情况了。此时,getBean方法就会得到A对象。返回到创建b的doCreate方法中后,此时populateBean结束,属性赋值完成。继续返回。
在这里插入图片描述
此时就会是这样的一种情况,因为我们刚刚是在对b的a属性赋值,但是a并没有给属性赋值。所以a下的b的值为null。继续返回到getSingleton方法之后。继续执行。
在这里插入图片描述
下面有这样的一个方法,我们点进去。
在这里插入图片描述
这里就比较明白了,就是向一级缓存中放数据。然后返回。
在这里插入图片描述
此时的三级缓存没有数据。
在这里插入图片描述
此时再执行这个方法的时候。
在这里插入图片描述
直接向一级缓存中找数据,发现找到了b对象,直接返回。返回到a对象属性赋值的时候。此时已经得到了一个完整的b对象。然后执行完A对象的populateBean方法之后,属性赋值完成。
在这里插入图片描述
此时的bean就是这样的情况。
最后返回到A的getSingleton方法中后,一样的还会执行addSingleton方法。
在这里插入图片描述
一级缓存中添加a,二级缓存中移除a。
在这里插入图片描述
此时就是这样的情况了。至此,对象创建结束。循环依赖完成。

总结

最后,我们来总结一下这个整体的流程。Spring通过三级缓存解决了循环依赖。一级缓存为单例池,二级缓存为早期曝光对象,三级缓存为早期曝光对象工厂。当A、B两类发生循环引用,在A实例化之后,将自己提早曝光(即加入三级缓存)。当A进行属性注入时,经过之前实例化步骤,此时轮到B属性注入,调用getBean(a)获取A对象,由于A处理正在创建集合中,此时也发了循环依赖,所以可以从三级缓存获取对象工厂(如果A被AOP代理,此时返回就是代理对象),并把对象放到二级缓存中。接下来,B走完Spring生命周期流程,并放入单例池中。当B创建完后,会将B注入A,A走完Spring生命周期流程。到此,循环依赖结束。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值