Spring的循环依赖解决方案

Spring的循环依赖解决方案

一、什么是Spring的三级缓存

在Spring的BeanFactory体系中,BeanFactory是Spring IoC容器的基础接口,其中DefaultSingletonBeanRegistry类实现了BeanFactory接口,并且维护了三级缓存:

image-20240820173324270

singletonObjects是一级缓存,存储的是完整创建好的单例Bean对象。在创建一个单例Bean时,会先从singletonObjects中尝试获取该bean的实例,如果能获取到就直接返回,否则继续创建该bean。

singletonFactories是三级缓存,存储的是单例Bean的创建工厂。当一个单例bean被创建时,Spring会先将该bean的创建工厂存储到singletonFactories中,然后再执行创建工厂的getObject()方法,生成该bean的实例对象。在该bean被其他bean引用时,Spring会从singletonFactories中获取该bean的创建工厂,创建出该bean的实例对象,并将该bean的实例对象存储到earlySingletonObjects中。

earlySingletonObjects是二级缓存,存储的是尚未完全创建好的单例Bean对象。在创建单例bean时,如果发现该bean存在循环依赖,则会先创建该bean的”半成品”对象,并将”半成品”对象存储到earlySingletonObjects中。当循环依赖的bean创建完成后,Spring会将完整的bean实例对象存储到singletonObjects中,并将earlySingletonObjects中存储的代理对象替换为完整的bean实例对象,这样可以保证单例bean的创建过程不会出现循环依赖问题。

二、三级缓存是如何解决循环依赖问题

Spring中Bean的创建过程其实可以分为两步,第一步叫做实例化,第二步叫做初始化。实例化的过程只需要调用构造函数把对象创建出来并分配空间,而初始化则是给对象的属性进行赋值。

Spring之所以可以解决循环依赖就是因为对象的初始化可以延后,也就是说,当创建一个Bean ServiceA的时候,会先把这个对象实例化出来,然后再初始化其中的serviceB属性。

1
2
3
4
5
6
7
8
9
10
11
@Service
public class ServiceA {
@Autowired
private ServuceB serviceB;
}

@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}

而当一个对象只进行实例化,但是还没有进行初始化时,我们称之为半成品对象。所谓半成品,其实只是bean对象的一个空壳子,还没有进行属性注入和初始化。

img

接下来说一下 Spring 创建 Bean 的流程:

  1. 先去 一级缓存 singletonObjects 中获取,存在就返回;
  2. 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;
  3. 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,获取成功之后,从三级缓存移除,并将该对象加入到二级缓存中。

只用两级缓存够吗? 在没有 AOP 的情况下,确实可以只使用一级和三级缓存来解决循环依赖问题。但是,当涉及到 AOP 时,二级缓存就显得非常重要了,因为它确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象,从而避免了同一个 Bean 有多个代理对象的问题。

不过,这种机制也有一些缺点,比如增加了内存开销(需要维护三级缓存,也就是三个 Map),降低了性能(需要进行多次检查和转换)。并且,还有少部分情况是不支持循环依赖的,比如非单例的 bean 和@Async注解的 bean 无法支持循环依赖。