java中的循环依赖以及解决方案

java中的循环依赖以及解决方案

什么是循环依赖

我们创建了两个类,一个类A,一个类B(当然循环依赖不一定只是两个类,可能是多个以及以上的类),在A里面引入了B,在B里面引入了A,这时候就构成了一个循环问题,看似无止境,无尽头
java中的循环依赖以及解决方案_第1张图片
他们之间的关系如下
java中的循环依赖以及解决方案_第2张图片

spring循环依赖解决方案

这些类在我们spring框架启动的时候,会进行有序的扫描并实例化
拿A举例:
1.实例化A
2.将a对象放入到指定的三级缓存对象
3.setName…当到成员变量b的时候,发现B上面有@Atowired注解,就会触发B的实例化过程

  • 实例化B
  • 将b对象放入到指定的三级缓存对象
  • setName…当到成员变量a的时候,就会去spring的三级缓存里面直接去拿
  • B的实例化完成

4.拿到b的实例化对象

注意:构造器注入构成的循环依赖是无法解决的!只能抛出BeanCurrentlyInCreationException异常表示循环依赖,构造函数之间发生循环依赖(A的构造方法中依赖B,B的构造方法中依赖A),程序会在运行时一直循环调用最终导致内存溢出。
错误原因:
根本原因是 Spring 解决循环依赖依靠的是 Bean 的 中间态 这个概念,而中间态指的是 已经实例化,但未初始化的状态。而构造器负责的是实例化,故无法解决构造器循环依赖。

spring的三级缓存

在Spring框架中,有一个名为"三级缓存"(Three-level Cache)的概念,用于管理Bean的创建和解决循环依赖的问题。

三级缓存是指在Spring容器实例化Bean的过程中,使用了三个缓存来管理Bean的创建和依赖关系。具体来说,这三个缓存分别是singletonObjects、earlySingletonObjects和singletonFactories。

singletonObjects:这是一级缓存,用于保存已经完全初始化的单例Bean的实例。当Bean创建完成后,它将被放入此缓存中,并且可以通过Bean名称进行访问。

earlySingletonObjects:这是二级缓存,用于保存已经实例化但尚未完全初始化的单例Bean的实例。当Spring框架检测到循环依赖时,会首先将已实例化但未初始化的Bean放入该缓存中,以便后续的依赖注入。一旦所有的依赖都已注入完成,Bean将从此缓存中移除,并放入一级缓存中。

singletonFactories:这是三级缓存,用于保存Bean的工厂类实例。当Spring框架检测到循环依赖时,会先创建一个Bean的工厂类实例并放入该缓存中。这样,当另一个Bean需要引用该Bean时,可以通过工厂类来获取实例,并在后续的实例化过程中完成循环依赖。

通过使用这三个级别的缓存,Spring框架可以在解决循环依赖问题时进行适当的处理,确保Bean的实例化和依赖注入的顺序正确。同时,三级缓存也有助于提高性能,避免重复创建Bean实例。但需要注意的是,三级缓存是Spring框架内部使用的机制,对开发者来说通常是透明的,不需要直接操作或干预。

在Java中,使用三级缓存是解决循环依赖问题的一种方案,通常被称为"提前暴露引用(Early Reference)"方案。

循环依赖是指两个或多个Bean之间相互依赖的情况,在实例化过程中可能会造成死锁或无法正确注入依赖的问题。为了解决这个问题,Spring框架引入了三级缓存机制,并且使用"提前暴露引用"的方式来处理循环依赖。

"提前暴露引用"方案的基本思想是,在实例化Bean的过程中,先创建一个早期的、未完全初始化的Bean实例,并将其放入二级缓存中。然后,在完成当前Bean的依赖注入之后,再从二级缓存中取出该早期实例并进行初始化。这样,可以通过提前暴露引用的方式解决循环依赖的问题。

在三级缓存中的三个级别,二级缓存即为保存早期实例的缓存,通过它来实现"提前暴露引用"的方案。当依赖注入完成后,早期实例会转移到一级缓存中,而三级缓存中的工厂类实例则会被移除。

需要注意的是,三级缓存和"提前暴露引用"方案都是Spring框架内部实现的,对于开发者来说通常是透明的。开发者只需按照正常的依赖关系进行编码,Spring框架会根据三级缓存机制来解决循环依赖问题。

你可能感兴趣的:(java,spring,mybatis)