Spring中的循环依赖

  • 循环依赖条件
  • 解决方案
  • Spring的一些容器
  • 不允许循环依赖出现的情况
  • 1、构造器注入
  • 2、多例对象的setter注入无法循环依赖
  • 数据结构支撑
  • 流程简单解析
  • 处理流程
  • 为什么需要二级缓存
  • 不支持循环依赖的原因
  • 提前暴露对象的条件


循环依赖条件

对象之间彼此的相互引用

例如A持有B对象,同时B也持有A对象

Spring 构造器注入map spring构造器注入循环依赖_循环依赖


也存在多个对象相互引用;例如A持有B,B持有C,C持有A的情况。

Spring 构造器注入map spring构造器注入循环依赖_循环依赖_02

解决方案

Spring中解决循环依赖使用了三个缓存(即一二三级缓存),并且通过提前暴露对象的手段使未进行依赖注入的对象可用。

Spring的一些容器

Spring 构造器注入map spring构造器注入循环依赖_Spring 构造器注入map_03

不允许循环依赖出现的情况

当然了,并不是说任何时候都能允许循环依赖的出现。下面就简单说两种

1、构造器注入

A,B两个对象作为构造函数的参数传入,并在构造函数进行依赖注入;会导致不支持循环依赖。

代码示例

A 对象

@Component
public class A {

    private B b;

	// 在构造器标注自动装配
    @Autowired
    public A(B b) {
        this.b = b;
    }
}

B 对象

@Component
public class B {

    private A a;

	// 在构造器标注自动装配
    @Autowired
    public B(A a) {
        this.a = a;
    }
}

报出的错误

经过简单的筛选,以下错误就是问题的核心。

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	
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?

核心问题

Spring报错直接指出是否存在了循环依赖导致无法正常进行Bean的实例化

Spring 构造器注入map spring构造器注入循环依赖_Spring 构造器注入map_04

简单原理

为什么通过构造器注入无法进行循环依赖呢?

因为Spring在创建对象的时候还未实例化对象就要注入另一个对象了;注意,是实例化。

当需要注入另一个对象的时候就需要去创建另一个对象;但是另一个对象也持有自己,所以形成了套娃。

Spring 构造器注入map spring构造器注入循环依赖_spring_05

2、多例对象的setter注入无法循环依赖

第二种情况就是多例的Bean对象无法进行循环依赖

代码示例

A 对象

@Component
@Scope("prototype")
public class A {
    @Autowired
    private B b;
}

B对象

@Component
@Scope("prototype")
public class B {

    @Autowired
    private A a;
}

main方法,需要注意一定要去手动获取对象

public class SpringCon {
    public static void main(String[] args) 

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
        // 一定要去获取Bean对象
        // 因为在Spring容器中,多例对象是用到的时候才去获取的
        // 如果不用该对象,那么不会创建;也就无法测试循环依赖
        Object a = context.getBean("a");


    }
}

异常错误

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

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?

无法循环依赖的原因

因为在多例中,不知道要对哪个对象进行循环依赖。

所以Spring在循环依赖的条件中就把多例排除出去了。

后面源码会稍微讲一下。

===================================================

以下的模块需要对Spring的生命周期有所简单的了解。

大概从源码角度简单分析,涉及到的类还未贴出

===================================================

数据结构支撑

• 循环依赖使用了三个缓存结构来解决,并不是说一定要三个,其实一三就能解决,多个二级缓存只是为了提高效率;并且三级缓存是一个工厂对象

• 其次,还涉及到了一个容器,这个容器是表示正在实例化的BeanDenfinition以及一个变量,就是允许提前暴露Bean实例。

三个Map图示(从上到下分别是一二三级缓存)

Spring 构造器注入map spring构造器注入循环依赖_实例化_06

Spring 构造器注入map spring构造器注入循环依赖_ioc_07

流程简单解析

首先,要清楚:如果一个类中包含成员变量拥有引用类型,并且加上了@Autowire的注解,那么在进行依赖注入的时候一定会实例化对应的引用对象,即beanFactory.getBean(被引用的BeanName);

循环依赖要涉及到Spring的IOC,DI,依赖注入的问题。

简单说明一下Bean对象的创建:
解析XML->通过无参构造器实例化对象->收集Bean对象的成员变量的注解->提前暴露Bean对象->依赖注入->放入一级缓存->后期销毁

创建对象图例:

Spring 构造器注入map spring构造器注入循环依赖_实例化_08

处理流程

假设下面是A对象走的流程

Spring 构造器注入map spring构造器注入循环依赖_ioc_09


Spring 构造器注入map spring构造器注入循环依赖_实例化_10


Spring 构造器注入map spring构造器注入循环依赖_spring_11


此时,依赖注入就会触发到另一个对象B的创建,也是跟上面一样的。

当B对象也进行依赖注入的时候,发现需要注入A,然后我们走到前面

Spring 构造器注入map spring构造器注入循环依赖_ioc_12


Spring 构造器注入map spring构造器注入循环依赖_循环依赖_13

为什么需要二级缓存

这里要说明一下为什么会有二级缓存这种东西
我们可以看到,三级缓存返回的是一个工厂,ObjectFactory
他的getObject方法比较复杂,所以直接将对象获取出来提取到一个中间缓存进行存放。

这样,如果A注入了很多循环依赖对象,那么其他的Bean需要拿到A的实例只需要从二级缓存中拿取实例对象即可,不需要去三级缓存拿工厂在生成,避免了一些重复的繁琐操作。

Spring 构造器注入map spring构造器注入循环依赖_实例化_14

不支持循环依赖的原因

• 首先来解析构造函数
1) 单例能够实现循环依赖是因为他已经实例化好了对象,只是没有给对象中的属性赋值,那么这个地址是永远都可以使用的

2)而通过构造函数注入,就需要传入该对象才能成功的实例化对象,否则无法进行对象的实例化
3) 小结:setter方法注入是已经实例化好了对象;构造器注入则是缺少实例化对象的条件,导致无法实例化,产生了死锁才报错

• 多例不支持的原因

1) 因为多例不能提前暴露对象,所以当第二次进来的时候,会发现我这个对象已经正在实例化了,所以报错了

2)并且多例对象天生就具有懒加载

Spring 构造器注入map spring构造器注入循环依赖_ioc_15

提前暴露对象的条件

单例的,允许循环依赖,该单例对象正在实例化

Spring 构造器注入map spring构造器注入循环依赖_实例化_16