spring三级缓存解决循环依赖源码

13 篇文章 0 订阅
5 篇文章 0 订阅

        当spring创建的对象A依赖于对象B,同时对象B又依赖于对象A,那么他们之间的相互依赖就会形成闭环,那么此时就会出现栈溢出异常。假设没有三级缓存,首先spring创建对象A,完成实例化后开始初始化注入属性,发现属性需要B对象,那么spring就应该先查看容器中是否含有创建好的B对象,很显然此时没有,那么spring就会转去创建B对象,当spring开始创建B对象时又发现B对象需要注入A对象,那么spring就会查看容器中是否有创建好的A对象,很显然没有(上一个A对象还在创建着呢)所有又会去创建A对象......如此循环往复。

        spring如何使用三级缓存完成闭环依赖对象的创建呢?

基础准备

类A

public class A {

    private B b;

    public B getB() {
        return b;
    }

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

类B

public class B {

    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="a" class="com.ling.spring5.domain.A">
        <property name="b" ref="b"></property>
    </bean>

    <bean id="b" class="com.ling.spring5.domain.B">
        <property name="a" ref="a"></property>
    </bean>
</beans>

测试

public void test18(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean4.xml");
        A a = ac.getBean(A.class);
        B b = ac.getBean(B.class);
        System.out.println(a);
        System.out.println(b);
        System.out.println(a.getB());
        System.out.println(b.getA());
    }

开始debug

        直接快进到创建对象finishBeanFactoryInitialization(beanFactory);

        beanFactory.preInstantiateSingletons();

        this.getBean(beanName);

        return doGetBean(name, null, null, false);

        此时位于doGetBean中,我在学习过spring的工作流程之后可以知道,doGetBean就是用来获取bean对象的方法,具体步骤如下:首先getBean-->doGetBean-->createBean-->doCreateBean

        进入后重点来了,此时还没有创建对象,我们看看在createBean之前spring做了什么,目光聚集到getSingleton方法,进入。

        继续进入 

        这个方法就尤为重要了,首先spring从一级缓存singletonObjects集合中拿出a对象,判断a对象是否为空,如果为空那么在判断a对象是否在创建中。我们知道此时我们还没有开始创建a对象,所以a对象时不可能在创建中的。所以方法直接return了

        回到上一个方法,后面他使用了匿名内部类的形式创建了一个对象,并且将当前对象,a的名称,a的beanName和BeanDefinition都传入了进去,在getSingetlon中又调用了该匿名内部类的getObject方法,也就是Lambda表达式(特殊的匿名内部类,必须是接口,且该接口只需重写一个方法),所以在getSingleton方法中调用getObject方法时,又会回到Lambda表达式这里,开始执行createBean方法。

        进入createBean开始创建A对象

        doCreateBean

        doCreateBean方法中前半部分都是创建对象的,所以我就略过了,那部分已经看了很多次了,直接看注入属性

        在方法的最后有个applyPropertyValues(beanName, mbd, bw, pvs);

        获取属性list

        开始注入属性,a对象只有一个属性值,那就是b 

        Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);

        来到1699行,本行是注入属性中的重要行,他的方法直译一下就是resolveValueIfNecessary如果可能的话就解决属性值的问题

        观察控制台中的pv属性和originalValue属性,发现都是与a的属性b相关的,那么进入这个方法来一探究竟。

        很明显,originalValue是RuntimeBeanReference类型的,方法第二步是类型转换,那么重点内容就是返回的时候调用的方法。

        该方法的330行,出现了通过BeanFactory获取对象的,那么这句话的意图就十分明显了,这步就是获取b对象的操作,点进去发现,又是getBean。

        现在又开始重复调用doGetBean方法了,甚至传参都一样的,回忆一下doGetBean方法,方法进入时会首先调用getSingleton方法,从一二三级缓存中获取,如果获取不到,那么就创建对象,所以此时对于对象b来说,和a对象创建一样的。又是getBean-->doGetBean-->createBean-->doCreateBean

        最后在doCreateBean中完成层对象的创建final Object bean = instanceWrapper.getWrappedInstance();此时的bean就是b对象,那么后面就是给b对象赋属性了

        在注入属性前先把b对象的转化为匿名内部类的lambda对象,然后同样的存入三级缓存中。

 

        同样的向下进入pupolate方法的applyPropertyValues方法,同样的开始注入属性,调用resolveValueIfNecessary方法,开始向b对象中注入a属性。

        进入return的方法

         这是第二次调用BeanFactory的getBean方法。第一次是a对象注入b对象时调用了该方法。

        进入后又是doGetBean方法,这就是第三次调用doGetBean方法了,第一次是获取a对象时调用,第二次是a需要注入b时调用。本次是b需要注入a时调用

         此时的三级缓存是有东西的,所以getSingleton方法的结局已经不同了

         进去 看看到底哪里不一样了。

        进入后首先从一级缓存中去取名称为a的键对应的value值,然后发现if中一级缓存没取到,同时a正在创建中(a实力化好了之后注入b,发现没有b,那么创建b,开始在b中注入a,才到了这一步,所以a还正在初始化过程中) 

        接着向下,这里从三级缓存中确实取出了一个东西

        看控制台,取出的正是在初始化a的时候传入的匿名内部类对象,而Lambda表达式的匿名内部类重写了父接口唯一的getObject方法,该方法将匿名内部类对象转化为了真正的A对象,那么后面就是把这个真正实例化后的a注入b对象中,同时把a放入二级缓存(这是a对象的半成品)然后除去三级缓存中的匿名内部类对象。

 

        回到注入属性的applyPropertyValues方法,此时已经拿到了注入属性所必要的a对象,一直向下,直到发现setPropertyValues方法,经过此方法后,b对象的a属性就被赋值成功了

        赋值成功后,我们可以展开想想了,赋值成功后放哪呢?三级缓存存储了创建半成品对象用的Lambda表达式匿名内部类对象,二级缓存存储了半成品对象,而一级缓存至今没有使用过,那一级缓存是不是将要存放完成品对象呢?

        继续往后,回到a为了注入b而调用的创建b的doCreateBean方法中 ,这不是doGetBean中啊,这是doCreateBean中,doGetBean中调用的是getSingleton(String),然后在此方法中调用重载的getSingleton(String,boolean),从二级缓存中获取b,而此时b只有在三级缓存中才存在,那么肯定是获取不到的,返回null。

        随后他将完成品的bean拿去注册了,难道要放在一级缓存了吗?我们进去看看。 

        发现没有,虚惊一场,来到了返回。一直返回来到初始化a时获取b的doGetBean方法,此时正在获取单实例b,getSingleton方法中,刚刚的一系列操作都发生在匿名内部类的getObject方法中

         最后我们看到了addSingleton

         进入该方法后发现,他把完成品b放在了一级缓存中并且清空了二级和三级缓存

        方法执行完回到初始化a时的doGetBean,此时a拿到了彻底完成初始化后的b,也就是sharedInstance对象,并将其加工后返回

         一路返回。

        方法终于回到了a在注入属性时调用的applyPropertyValues方法中,此时刚执行完 解决属性注入 方法。

        此时已经拿到了b完整对象的值

         最后来到熟悉的setPropertyValues方法。

 

执行前:

执行后:

 

         a对象也完成了赋值,循环依赖问题解决完成。

        现在来总结一下所扒源码的具体流程,首先容器启动,开始创建a对象,循环遍历需要创建的名单,拿到了第一个beanName:a开始调用getBean方法创建a,getBean中调用doGetBean完成整体创建,doGetBean中首先调用getSingleton方法,当一级缓存(完成品集合)中存在了就直接获取a,显然没有。那么此时调用getSingleton方法的目的就结束了(此时只是看看一级缓存中有没有a对象,至于二三级怎么样完全可以不看)。没取到a对象,那么就开始创建a对象,创建a对象同样用到了getSingleton方法,此方法不是上文重载的从缓存中查找a对象的方法,而是创建a对象的方法,方法传入了两个值,前者是需要创建的bean的名称,也就是a,后面是ObjectFactory<?>接口的匿名内部类对象(该对象重写了getObject方法,里面加装了createBean的操作)。getSingleton中调用了匿名内部类重写的getObject方法,方法中调用的createBean方法,createBean方法中调用了doCreateBean方法,此方法中的getWrappedInstance方法完成了bean的创建实例化。此时a已经被实例化了,只是还没有属性。此后spring想把半成品对象a加入到三级缓存中,于是就调用了addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)方法,传入了匿名内部类(同样是ObjectFactory<?>接口的实现类,该匿名内部类在getObject方法中调用了 getEarlyBeanReference)将该匿名内部类存在了三级缓存中。(匿名内部类的getEarlyBeanReference方法用于获取对应的真实对象)。

        然后开始populateBean注入b属性。populateBean调用了applyPropertyValues方法。注入b时发现b对象不存在,那么就resolveValueIfNecessary解决b不存在问题,resolveValueIfNecessary中调用了resolveReference,在该方法中调用了BeanFactory对象的getBean方法,这个方法中又像刚创建a时一样,调用了doGetBean方法,然后就是同样重复的操作。

        重复创建b后,b的Lambda对象也像a的Lambd对象一样,放到了三级缓存中,此时开始为b的a属性注入依赖。此时有来到注入属性时所需要的resolveValueIfNecessary来获取属性对象,resolveValueIfNecessary中调用了resolveReference,在该方法中调用了BeanFactory对象的getBean方法,这个方法中又像刚创建a时一样,调用了doGetBean方法,然后就是同样重复的操作。

        此时理一理思路,为什么我们有来到了getBean。第一次是创建a的时候我们路过了getBean,第二次是a注入属性b的时候,调用了getBean获取b,此时是第三次,由于没获取b,在创建b的时候注入a时又使用了doGetBean。

        第三次进入doGetBean后一切变得不一样了。首先就是getSingleton,此时三级缓存中已经有了两个对象所以getSingleton是可以获取到与a对象有关的匿名内部类对象的。获取了匿名内部类对象后调用了getObject方法,重写后的getObject方法中只有一个getEarlyBeanReference方法,此方法就是用于获取真正的a对象,getSingleton方法中取出了三级缓存的a匿名内部类,转化为了a半成品对象,将其作为返回值,并且将a的半成品对象放到了二级缓存中。拿到bean后一路返回,回到了给b注入属性的applyPropertyValues方法中,调用了最后的setPropertyValues完成了a的注入。

        调用完毕后带着完整的b对象一路返回,返回到createBean创建b,此时b已经完成了创建,addSingleton随后使用addSingleton,将b放入一级缓存,放入后带着完成品b一路返回。返回到a注入b时的applyPropertyValues调用了最后的setPropertyValues完成了b的注入。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aristocrat l

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值