Spring是怎么处理循环依赖的?

博客 分享
0 255
张三
张三 2022-02-12 19:55:16
悬赏:0 积分 收藏

Spring 是怎么处理循环依赖的?

从Java的视角和Spring的视角来审视循环依赖的问题。一起来看看以构造函数的方式进行循环依赖出错的原因,以及以Set的方式解决循环依赖的原理。

Java语法中的循环依赖

首先看一个使用构造函数的循环依赖,如下:

public class ObjectA {    private ObjectB b;    public ObjectA(ObjectB b) {        this.b = b;    }}public class ObjectB {    private ObjectA a;    public ObjectB(ObjectA a) {        this.a = a;    }}public class Main {    public static void main(String[] args) {        //ObjectB b = new ObjectB(new ObjectA(new ObjectB()));    }}

大家可以看上面这个例子,可以看出是没有办法new出ObjectA或者ObjectB的

那怎么解决上面的例子呢?如下:

public class ObjectA {    private ObjectB b;    public void setB(ObjectB b) {        this.b = b;    }    public ObjectB getB() {        return this.b;    }}public class ObjectB {    private ObjectA a;    public void setA(ObjectA a) {        this.a = a;    }    public ObjectA getA() {        return this.a;    }}public class Main {    public static void main(String[] args) {        ObjectB b = new ObjectB();        ObjectA a = new ObjectA();        b.setA(a);        a.setB(b);        System.out.println(a + " " + a.getB());        System.out.println(b + " " + b.getA());    }}

输出如下:

cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460 cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d

可以看出把构造函数去掉,然后增加set方法就可以实现循环依赖的问题了。

Spring 的构造函数循环依赖

测试例子如下:

@Componentpublic class MyBeanOne {    private MyBeanTwo myBeanTwo;    @Autowired    public MyBeanOne(MyBeanTwo myBeanTwo) {        this.myBeanTwo = myBeanTwo;    }}@Componentpublic class MyBeanTwo {    private MyBeanOne myBeanOne;    @Autowired    public MyBeanTwo(MyBeanOne myBeanOne) {        this.myBeanOne = myBeanOne;    }}public class Main {    public static void main(String[] args) {        AnnotationConfigApplicationContext context =                new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class);        MyBeanOne myBeanOne = context.getBean(MyBeanOne.class);        MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class);        System.out.println(myBeanOne);        System.out.println(myBeanTwo);    }}

输出如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'myBeanOne': Requested bean is currently in creation: Is there an unresolvable circular reference?	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)

DefaultSingletonBeanRegistry.beforeSingletonCreation()里打断点,看一下为什么出错了:
第一次经过:

第二次经过:

第三次经过:

我们可以猜测出错的原因是这样的:

去生成myBeanOne,需要生成myBeanTwo去生成myBeanTwo,需要生成myBeanOne去生成myBeanOne,发现myBeanOne已经在创建中了

Spring 的Set方式循环依赖

@Componentpublic class MyBeanOne {    @Autowired    @Getter    private MyBeanTwo myBeanTwo;}@Componentpublic class MyBeanTwo {    @Autowired    @Getter    private MyBeanOne myBeanOne;}public class Main {    public static void main(String[] args) {        AnnotationConfigApplicationContext context =                new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class);        MyBeanOne myBeanOne = context.getBean(MyBeanOne.class);        MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class);        System.out.println(myBeanOne + " " + myBeanOne.getMyBeanTwo());        System.out.println(myBeanTwo + " " + myBeanTwo.getMyBeanOne());    }}

输出:

cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114 cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869 cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114

可以看出通过set注入的方式是可以解决循环依赖问题的。

我们继续在DefaultSingletonBeanRegistry.beforeSingletonCreation()里打断点,看一下set方式是怎么经过这里的:
发现只经过了两次,没有第三次,如果有第三次的话,也就抛异常了,所以只经过两次是正常的。

为什么构造函数有三次,而set方式有两次?

我们看一下DefaultSingletonBeanRegistry.beforeSingletonCreation的调用链:

AbstractBeanFactory.doGetBeanDefaultSingletonBeanRegistry.getSingletonDefaultSingletonBeanRegistry.beforeSingletonCreation

那我们就在AbstractBeanFactory.doGetBean这里打断点,看一下set方式,为什么没有第三次
第一次:

第二次:

第三次:

我们可以看到第三次的myBeanOne已经有值了,它就不会执行到DefaultSingletonBeanRegistry.beforeSingletonCreation
如果换成构造方式来调试的话,在第三次,myBeanOne依旧是null的,就会继续往下执行到DefaultSingletonBeanRegistry.beforeSingletonCreation,然后就会抛错。

我们来看一下第三次的myBeanOne是怎么获取的:
DefaultSingletonBeanRegistry.getSingleton 方法如下:

可以看到是通过this.singletonFactories.get(beanName)得到一个工厂,通过这个工厂可以创建出对应的bean

我们再来看一下这些个工厂是什么时候被放进去的,DefaultSingletonBeanRegistry.addSingletonFactory

为什么通过构造函数注入的方式,没有提前放入一个工厂

再执行AbstractAutowireCapableBeanFactory.createBeanInstance 方法时

set方式会执行以下:

调用这个方法之后,回退到 AbstractAutowireCapableBeanFactory.doCreateBean() 继续往下执行,会把工厂放进去。

构造函数的方式会执行以下:

调用这个方法,后面会继续获取myBeanTwo,如下:

然后同理再获取myBeanOne,就会抛异常了,它不会回退到 AbstractAutowireCapableBeanFactory.doCreateBean(),自然也不会把工厂放进去。

最后理一下

对于Set方式,当类构造好之后,会提前把生成这个类的工厂放到缓存中;而构造函数的方式,由于存在构造函数,必须在当下去获取依赖类,所以就没办法构造类,其实原理和刚开始举的Java的例子是一个道理。

参考

Spring 解决循环依赖必须要三级缓存吗?
Spring循环依赖三级缓存是否可以去掉第三级缓存?
Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

posted @ 2022-02-12 19:35 eaglelihh 阅读(4) 评论(0) 编辑 收藏 举报
回帖
    张三

    张三 (王者 段位)

    821 积分 (2)粉丝 (41)源码

     

    温馨提示

    亦奇源码

    最新会员