文章目录
什么是Spring的循环依赖
Spring 框架中的循环依赖指的是在 Spring 容器中,两个或多个 bean 之间存在相互依赖的情况。这种依赖关系会导致循环引用,使得 Spring 需要在创建这些 bean 时采取特殊的处理措施。
例如,假设有两个 bean A 和 B,其中 A 依赖于 B,而 B 又依赖于 A。如果不加以处理,这种情况会导致创建 bean 的过程中出现问题,因为每个 bean 的创建都依赖于另一个尚未完全创建的 bean。
public class B {
private A a;
}
public class A {
private B b;
}
Spring是如何解决的(三级缓存)
构造器注入的循环依赖
对于构造器注入,Spring 无法自动解决循环依赖,因为在构造函数调用时,依赖的 bean 尚未完全初始化。因此,构造器注入的循环依赖一般会导致 BeanCurrentlyInCreationException 异常。为了解决这个问题,可以选择使用 setter 注入或构造函数注入来避免这种情况。
Setter 注入或属性注入的循环依赖:
使用注解方式(例如 @Autowired)时,Spring 默认使用的是 setter 注入方式(在字段上也可以使用 @Autowired,它实际上是 setter 注入的一种简化)
对于 setter 注入,Spring 能够通过三级缓存机制来解决循环依赖问题。
三级缓存机制
Spring 在创建 bean 的过程中维护了三个主要的缓存:
-
一级缓存
- 功能:存储完全初始化的 bean 实例。这个缓存保证了一个 bean 在 Spring 容器中是唯一的,并且在容器中被引用时是完全初始化的。
- 源码:在 DefaultSingletonBeanRegistry 类中,一级缓存是通过 singletonObjects 维护的。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
-
二级缓存
- 功能:存储正在创建中的 bean 实例的引用。当一个 bean 被创建但尚未完全初始化时,它会被放入这个缓存中,以供其他 bean 访问(解决循环依赖)。
- 源码:在 DefaultSingletonBeanRegistry 类中,二级缓存是通过 earlySingletonObjects 维护的。
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
-
三级缓存
- 功能:存储 bean 的工厂方法引用。这个缓存用于处理正在创建中的 bean 的循环依赖问题。它确保在 bean 实例创建期间,其他 bean 可以获得一个工厂方法来完成 bean 的创建。
- 源码:在 DefaultSingletonBeanRegistry 类中,三级缓存是通过 singletonFactories 维护的。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
解决循环依赖的具体流程:
- 创建Bean的过程:
- 当Spring容器需要创建一个Bean时,会先检查singletonObjects中是否已有该Bean的实例。
- 如果没有,检查earlySingletonObjects和singletonFactories中是否存在正在创建中的Bean的代理对象。
- 三级缓存的使用:
- 步骤1: Spring先将Bean的Factory放入singletonFactories缓存中。
- 步骤2: 如果Bean的初始化过程中遇到循环依赖,Spring会从earlySingletonObjects中查找已经创建中的Bean的代理对象。
- 步骤3: 一旦Bean的创建完成,Spring会将该Bean从earlySingletonObjects中移除,并放入singletonObjects缓存中,以便其他Bean可以使用这个完全初始化好的实例。
三级缓存解决循环依赖问题的关键在于:
实例化和初始化分开操作,在中间过程中给其他对象赋值的时候,并不是一个完整对象,而是把半成品对象赋值给了其他对象。
相关源码
- 主要看DefaultSingletonBeanRegistry.java
辅助理解流程图:
以上A,B两个类互相依赖的例子,描述实例化,初始化,一级缓存,二级缓存,三级缓存,在初始化过程中的大概流程。
只用一级缓存能解决问题吗
不能!
在整个处理过程中,缓存中放的是半成品和成品对象,如果只有一级缓存,那么成品和半成品都会放到一级缓存中,有可能在获取过程中获取到半成品对象,此时半成品对象是无法使用的,不能直接进行相关的处理,因此要把半成品和成品的存放空间分割开来。
只用二级缓存能解决问题吗
如果我能保证所有的bean对象都不去调用getEarlyBeanReference此方式就可以。
使用三级缓存的本质在于缺乏Bean Factory的代理支持: 二级缓存仅存储正在创建中的Bean实例,但在某些情况下,Bean的创建可能涉及到代理对象(例如,Spring AOP创建的代理对象)。单独使用二级缓存无法处理这些代理对象的需求。
为什么使用了三级缓存就可以解决这个问题?
当一个对象需要被代理的时候,在整个创建过程中是包含两个对象吧。一个是普通对象,一个代理生成的对象,bean默认都是单例,那么我在整个生命周期的处理环节中,一个beanname能对应两个对象吗?不能,既然不能的话,保证我在使用的时候加一层判断,判断一下是否需要进行代理的处理。
因为不知道什么时候回调用,所以通过一个匿名内部类的方式,在使用的时候直接对普通对象进行覆盖操作,保证全局唯一!!!