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

x33g5p2x  于2022-02-12 转载在 Spring  
字(4.4k)|赞(0)|评价(0)|浏览(274)

从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@3fa77460
cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460 cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d

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

Spring 的构造函数循环依赖

测试例子如下:

@Component
public class MyBeanOne {
    private MyBeanTwo myBeanTwo;

    @Autowired
    public MyBeanOne(MyBeanTwo myBeanTwo) {
        this.myBeanTwo = myBeanTwo;
    }
}

@Component
public 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方式循环依赖

@Component
public class MyBeanOne {
    @Autowired
    @Getter
    private MyBeanTwo myBeanTwo;
}

@Component
public 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@35ef1869
cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869 cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114

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

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

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

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

AbstractBeanFactory.doGetBean
DefaultSingletonBeanRegistry.getSingleton
DefaultSingletonBeanRegistry.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 的循环依赖,源码详细分析 → 真的非要三级缓存吗

相关文章