Spring三级缓存解决循环依赖?构造方法的循环依赖问题解决(原理、详细过程、面试题)
目录
1.什么是循环依赖
2.Spring三级缓存介绍
3.二级缓存可以解决循环依赖吗?
3.1 二级缓存解决循环依赖过程详解
4. 为什么还需要三级缓存呢?/Spring三级缓存的作用?
4.1三级缓存解决循环依赖过程详解
4.2 为什么二级缓存就不能解决代理对象的循环依赖问题呢?/为什么使用三级缓存而不是直接放入二级缓存?
5.构造方法的循环依赖怎么解决?
6. 面试题
6.1 什么是Spring中的循环引用(循环依赖)?
6.2 Spring的一级三级缓存可以解决一般的循环依赖和代理对象的循环依赖,那二级缓存可以去掉吗?/有什么用?
6.3 构造函数出现循环依赖怎么解决?
1.什么是循环依赖
循环依赖是两个或者多个Bean相互持有对方的引用或者自己依赖自己,导致无法进行(属性)依赖注入,造成死循环。
循环依赖分为3种情况
1.A、B相互依赖
2.A、B、C相互依赖
3.A 自己依赖自己
2.Spring三级缓存介绍
一级缓存:存放完全实例化的bean
二级缓存:存放未完全实例化的bean(没有依赖注入的bean)
三级缓存:存放创建bean的工厂(主要为了支持AOP)
3.二级缓存可以解决循环依赖吗?
答:二级缓存可以解决不涉及代理对象的循环依赖问题(但只使用一级缓存不可以),如果涉及代理对象需要用到三级缓存。
一级缓存:限制bean在beanFactory中只存一份,即实现singletonscope单例bean,解决不了循环依赖。
二级缓存:存放未完全实例化的bean,
3.1 二级缓存解决循环依赖过程详解
★下面例子过程详解:
A进行实例化,并且把原始对象(未依赖注入)放入二级缓存中,进行依赖注入时,发现B还没有进行初始化(实例化),所以此时B开始实例化,并且把B的原始对象(未依赖注入)也放入二级缓存中,然后进行依赖注入(A a),B再把完全初始化的B对象放入一级缓存的单例池中,A进行依赖注入,并且把完全初始化的A对象放入一级缓存的单例池中,放入一级缓存后,二级缓存中的A,B对象都会被清除掉。
4. 为什么还需要三级缓存呢?/Spring三级缓存的作用?
spring三级缓存用来解决代理对象的循环依赖问题。
具体步骤如下:
4.1三级缓存解决循环依赖过程详解
假设我们有两个服务类A
和B
,它们之间存在循环依赖,并且类A
需要进行AOP代理。
@Service
public class A {
@Autowired
private B b;
public void methodA() {
System.out.println("Method A executed");
}
}
@Service
public class B {
@Autowired
private A a;
public void methodB() {
System.out.println("Method B executed");
}
}
同时,我们有一个AOP切面AspectA
,它对类A
的methodA
方法进行了增强。
@Aspect
@Component
public class AspectA {
@Before("execution(public void com.example.A.methodA())")
public void beforeA() {
System.out.println("Before method A executed");
}
}
具体步骤:
创建Bean A:
- Spring首先尝试创建Bean A,进行实例化。
- 在实例化之后,但属性注入之前,Spring将Bean A的工厂对象放入三级缓存中。
属性注入阶段:
- 在注入Bean B时,发现Bean B依赖于Bean A。
- 此时,Spring会尝试从一级缓存和二级缓存中获取Bean A,但都未找到。
- 由于Bean A正在创建中,Spring从三级缓存中获取Bean A的工厂对象,并调用
getObject()
方法获取Bean A的早期引用(可能是代理对象或原始对象)。Bean B的创建:
- 使用从三级缓存中获取的Bean A的早期引用,完成Bean B的属性注入。
- Bean B完成创建后,将其放入一级缓存中。
完成Bean A的创建:
- 回到Bean A的创建过程,使用Bean B的实例完成Bean A的属性注入。
- 如果有AOP增强,Spring会在
initializeBean(初始化Bean)
阶段为Bean A创建代理对象。- 将Bean A的代理对象放入一级缓存中,替换掉原始对象。
结果
最终,Bean A和Bean B都能正常创建,并且它们相互引用的是对方的代理对象(如果需要的话)。这样,Spring通过三级缓存机制解决了代理对象的循环依赖问题,同时保持了AOP代理的完整性
看到这里你可能有以下疑问了:
代理对象A刚开始创建实例的时候为什么不把实例化之后属性注入之前的原始对象放入二级缓存,而是把Bean A的工厂对象放入三级缓存中呢?即问题如下
4.2 为什么二级缓存就不能解决代理对象的循环依赖问题呢?/为什么使用三级缓存而不是直接放入二级缓存?
1.解决循环依赖:对象B通过获取代理对象A的工厂对象(来创建A的原始对象)来获取A的早期引用(实例化之后,属性注入之前)完成B对象属性注入。
详细如下:
- 当Bean B依赖于Bean A时,如果Bean A已经完全初始化,那么Bean B可以直接使用。但如果Bean A还没有完全初始化,Spring需要提供一个机制来提前暴露Bean A的引用,以便Bean B可以完成属性注入。
- 通过将Bean A的工厂对象放入三级缓存,Spring可以在Bean A完全初始化之前,提供一个途径来获取Bean A的早期引用(可能是原始对象或代理对象)。这样,当Bean B需要Bean A时,Spring可以通过工厂对象来获取这个早期引用,完成Bean B的属性注入。
看到这里你可能会问,你也并没有说明为什么非三级缓存不可呢?
别急,请看第二条
2. 延迟代理对象的创建:
- 代理对象应该在所有属性注入完成之后创建,以确保代理对象包含了(被代理对象)所有的代理逻辑。
- 如果Spring在实例化后立即将Bean A的原始对象放入二级缓存,那么在属性注入阶段,Bean B注入的将是Bean A的原始对象,而不是代理对象。这将导致Bean B中引用的Bean A缺失了AOP代理逻辑。
3.保持Bean生命周期的完整性,确保Bean A在被其他Bean引用之前,已经是一个完全初始化好的对象。
- Spring容器管理Bean的生命周期,包括实例化、属性注入、初始化等步骤。如果Spring在属性注入之前就将Bean A的原始对象放入二级缓存,那么可能会破坏这个生命周期,因为Bean A还没有完成所有初始化步骤。
- 通过使用三级缓存,Spring可以在Bean A完成所有初始化步骤后,再决定是否需要将其放入二级缓存或一级缓存。这样可以确保Bean A在被其他Bean引用之前,已经是一个完全初始化好的对象。
spring可以解决所有循环依赖吗?
不可以!
上面代码是通过setter方法完成属性注入的(在Spring框架中,当您使用
@Autowired
注解时,Spring会自动根据属性名称去寻找相应的setter方法,并调用它来完成依赖注入。)解决的是初始化过程中的循环依赖问题,但是构造方法注入属性时导致的循环依赖怎么解决?
5.构造方法的循环依赖怎么解决?
解决办法:
@Lazy 注解:延迟加载,什么时候需要该对象,就什么时候创建该对象,而不是实例化时把该对象注入进来,即什么时候用,什么时候创建。
6. 面试题
6.1 什么是Spring中的循环引用(循环依赖)?
6.2 Spring的一级三级缓存可以解决一般的循环依赖和代理对象的循环依赖,那二级缓存可以去掉吗?/有什么用?
1.提前曝光的Bean引用:
二级缓存用于存放那些已经实例化但还未完全初始化(属性注入未完成)的Bean。这允许在属性注入阶段,如果存在循环依赖,可以提前使用这些Bean的引用。
2.避免重复创建Bean(保证bean是单例的),保持引用一致性:
当一个Bean被请求时,如果它正在创建中(即构造函数已调用但属性注入未完成),Spring可以从二级缓存中获取这个Bean的早期引用,而不是重新创建一个新的实例。这避免了循环依赖导致的重复创建问题,确保了所有其他Bean 依赖的正在创建中的Bean 都是同一个实例。
举例:
例如如果二级缓存 不 存放未完全实例化的bean和代理对象,那每次使用代理对象都需要调用代理类的getObjec()方法来获取代理对象实例,可能导致代理对象是多例的。