一、对于循环依赖概述:
1、循环依赖:
简单来说:循环依赖是指一个Bean或者多个Bean实例之间,会存在直接或者间接的一个依赖关系,构成一个循环调用,也称为循环引用。
2.其形态主要分为以下三点:
① 互相依赖
//A注入了B
@Component
public class BeanA{
@Autowired
BeanB b;
}
//B也注入了A
@Component
public class BeanB{
@Autowired
BeanA a;
}
② 间接依赖
//注入B
@Component
public class BeanA{
@Autowired
BeanB b;
}
@Component
public class BeanB{
@Autowired
BeanA a;
@Autowired
BeanC c;
}
@Component
public class BeanC{
@Autowired
BeanA a;
}
② 自我依赖
@Component
public class BeanA{
@Autowired
BeanA a;
}
3、循环依赖的问题
在Spring框架中,循环依赖可能导致以下问题:
3.1. 实例化困难:如果两个或多个Bean相互依赖,Spring容器在尝试实例化这些Bean时可能会遇到困难,因为它需要按照一定的顺序来解决依赖关系。
3.2. 初始化失败:在Bean的初始化过程中,如果发现循环依赖,可能会导致属性赋值出错,进而引发初始化失败。
3.3. 破坏设计原则:循环依赖违反了面向对象设计中的依赖倒置原则和单一职责原则,使得模块之间的界限模糊,增加了系统的耦合度。
3.4. 运行时异常:在某些情况下,循环依赖可能导致运行时异常,尤其是在使用构造器注入时,Spring无法解决构造器级别的循环依赖。
二、解决循环依赖的方案
4、Spring循环依赖的解决方案:
在提出解决方案之前,可以先了解Spring bean的生命周期,Spring Bean的生命周期大体上分为三个阶段:
① Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的, 是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的
Bean通过反射进行实例化;
② Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware 接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段;
③ Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池
singletonObjects中去了,即完成了Spring Bean的整个生命周期。
三、三级缓存池概述
Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map
/** 一级缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 三级缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** 二级缓存 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
4、三个Map大致区别:
① Map类型不同:一级、二级缓存为ConcurrentHashMap,三级缓存池为HashMap
② 容量不同:一级缓存为256,二级为16,三级缓存也为16
③ 范型不同:一级以及二级为Object类型,三级缓存池为ObjectFactory类型
5、三级缓存包括:
概述:
① 一级缓存池(singletonObjects):存储已经完全实例化和初始化的Bean实例,可通过ApplicationContext接口的getBean() 方法,直接获取实例化对象。
② 二级缓存池(earlySingletonObjects):存储尚未完全初始化但已经足够用于属性注入的Bean实例,这些实例被称为“半成品”。
③ 三级缓存池(singletonFactories):存储创建Bean实例的工厂对象,和二级缓存池相似,都是用来存放暂时不完整的Bean对象的容器。但与二级缓存池有不同,此缓存池存放的对象没有被其他类引用,只存放不完整的Bean对象。
6、关于循环依赖简单案例
假设有两个Bean A 和 B,它们之间存在循环依赖:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
当Spring容器创建Bean A时,它会发现需要注入尚未完全初始化的Bean B。此时,容器会使用三级缓存机制:
- 实例化Bean
A
:在实例化后,容器会立即将BeanA
的半成品(尚未填充属性)放入二级缓存。 - 创建Bean
B
:在创建BeanB
的过程中,容器会发现需要注入BeanA
。由于BeanA
的半成品已经在二级缓存中,容器会继续实例化BeanB
。 - 循环依赖的解决:在Bean
B
的属性填充阶段,容器会从二级缓存中取出BeanA
的半成品,并将其放入一级缓存。这样,BeanB
就可以继续其初始化过程,而BeanA
也可以在稍后完成其初始化。
通过这种方式,Spring容器能够在不违反Bean的单例作用域和依赖注入原则的前提下,处理复杂的循环依赖情况.