阅读本文章之前推荐先阅读博主关于Bean生命周期的文章,传送地址:Spring中IOC容器和Bean的生命周期

循环依赖

文章目录

  • 循环依赖
  • 一、循环依赖的概念
  • 二、什么情况下可以解决循环依赖的问题
  • 三、Spring如何解决循环依赖
  • 1. 解决简单的循环依赖(没有AOP)
  • 2. 解决结合了AOP的循环依赖


一、循环依赖的概念

从字面上来理解就是A依赖B的同时B也依赖了A,如下图所示


spring循环依赖检测插件 spring循环依赖解决原理_spring boot

对应的代码如下:

@Component
public class A {
    // A中注入了B
    @Autowired
    private B b;
}

@Component
public class B {
    // B中也注入了A
    @Autowired
    private A a;
}

对应的流程图如下所示,可以看出发生了死循环:


spring循环依赖检测插件 spring循环依赖解决原理_spring boot_02

二、什么情况下可以解决循环依赖的问题

Spring解决循环依赖是有前置条件的:

  • 出现循环依赖的Bean必须是单例的
  • 依赖注入不能全是构造器注入的方式
@Component
public class A {
    //没有使用@Autowired注解
    private B b;
    //使用构造器注入
    public A(B b) {
        this.b = b;
    }
}


@Component
public class B {
	
    private A a;
    public B(A a){
        this.a = a;
    }
}

上述代码中两个Bean的注入方式都是通过构造器注入的,这种情况下循环依赖是无法被解决的,启动时会报错:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

  • 循环依赖的解决情况和注入方式的关系

从图中可以看出,均使用 setter 方法注入的情况循环依赖可以被解决;均采用构造器注入的情况无法被解决;使用了构造器注入的方式循环依赖也可能被解决

三、Spring如何解决循环依赖

Spring通过提前暴露对象(三级缓存)的方式解决循环依赖,大体过程如下图所示:


spring循环依赖检测插件 spring循环依赖解决原理_spring循环依赖检测插件_03

提前暴露的对象指的就是实例化完成但还没有初始化的对象,称为半成品对象

观察上图可以得出解决循环依赖最重要的两点是:

  • 实例化和初始化的过程分开
  • 设置缓存来预存半成品对象

1. 解决简单的循环依赖(没有AOP)

分析一个最简单的例子:

@Service
public class TestService1 {
    @Autowired
    private TestService2 testService2;
}

@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;
}
//这种情况下循环依赖一定可以被Spring自动解决

Spring内部有三级缓存:

  • singletonObjects,一级缓存(也称为单例池),用于保存实例化、注入、初始化完成的bean实例
  • earlySingletonObjects,二级缓存,用于保存完成实例化,但是还未进行属性注入及初始化的对象(半成品对象)
  • singletonFactories,三级缓存,是一个单例工厂,二级缓存中存储的半成品对象就是从这个工厂中获取到的

Spring解决循环依赖的流程图:

Spring在创建多个Bean的时候默认是按照自然排序来进行创建的,所以第一步Spring会去创建testService1:


spring循环依赖检测插件 spring循环依赖解决原理_spring_04

从上述的流程图可以得出如下结论:

  • 初始化完成的Bean最终会被放到一级缓存,也就是单例池中
  • 属性注入之前Spring将Bean包装成一个工厂(ObjectFactory)添加进了三级缓存中
  • 但实际上这个三级缓存在没有使用AOP的循环依赖中并没有实际用处,可以直接将对象放在二级缓存中
  • testService2中提前注入了一个没有经过初始化的TestService1类型的对象不会有问题吗?
  • 不会的,因为在创建 testService1 的流程中一直使用的是注入到 testService2 中的 testService1 对象的引用,之后会根据这个引用对 testService1 进行初始化,所以这是没有问题的

2. 解决结合了AOP的循环依赖

依然按照上述的例子,如果对testService1使用了AOP代理,则testService2从三级缓存中获取到的是testService1的代理对象(再将其移动至二级缓存中),而不是testSevice1的半成品对象

几个问题:

  • 在给testService2注入的时候为什么要注入一个代理对象?
  • 对testService1进行了AOP代理时,说明希望从容器中获取到的就是testService1代理后的对象而不是testService1本身,因此把testService1当作依赖进行注入时也要注入它的代理对象
  • 三级缓存真的提高了效率吗?
  • 对于没有使用AOP情况,之前已经分析过,没有提高任何效率
  • 对于使用AOP的情况
  • 如果使用了三级缓存,为testService1创建代理对象的时机是在testService2中需要注入testService1时
  • 如果没有使用三级缓存,在实例化testService1之后,就要为其创建代理对象
  • 对于两种情况的整体而言,无论有没有三级缓存消耗的时间都是一样的,故三级缓存提高了效率这种说法都是错误的