彻底理解Spring三级缓存机制
文章目录
- 前言
- 一、Spring解决循环依赖时,为什么要使用三级缓存?
前言
Spring解决循环依赖的手段,是通过三级缓存
:
- singletonObjects:存放所有生命周期完整的单例对象。(一级缓存)
- earlySingletonObjects:存放所有完成了
实例化
操作的早期单例对象。(二级缓存) - singletonFactories:存放单例工厂的对象,通过工厂创建早期Bean。(三级缓存)
具体Spring是如何解决循环依赖问题的,在Spring循环依赖源码分析中已经详细说明,本篇侧重于证明三级缓存的必要性。
一、Spring解决循环依赖时,为什么要使用三级缓存?
这是一道非常经典的面试题。如果一个对象需要被代理,那首先需要生成一个普通的对象,而代理对象和普通对象是不能同时存在于容器中的,当一个对象需要使用代理的时候,就要使用代理对象覆盖掉原来的普通对象。
这是一个典型的循环依赖场景,存在两个相互引用的 Bean:A 和 B。其中 A 包含 b 属性,B 包含 a 属性。无论是否存在循环依赖,这两个 Bean 在完成实例化后都会自动存入三级缓存。需要注意的是,通过反射创建的实例对象与放入三级缓存工厂中的对象实际上是同一个引用。
然后A对象执行属性注入,发现需要B属性,B在容器中是不存在的,于是需要去创建B对象。
B对象在执行属性注入,发现需要A属性,需要从容器中获取:
这里从三级缓存
中,可以获取到创建A对象的工厂方法,如果A对象需要AOP,则会:
为B的A属性赋值时,使用的是经过AOP处理的A对象。在B完成初始化后,A对象完成属性注入并继续初始化流程,此时不会再次进行AOP处理。这意味着A对象和B对象中的A属性实际上指向同一个经过AOP处理的对象实例。这就是三级缓存存在的意义。
如果仅使用二级缓存,给B的A属性赋值的是未经AOP处理的原始A对象。随后A继续完成后续生命周期并经过AOP处理,导致最终生成的A对象与B持有的A对象不同——前者经过AOP增强,而后者仍是原始对象。
这里可能会有一个疑惑的点,那就是,A对象和B中的A属性应该是同一个实例,如果A继续完成后续生命周期并执行AOP处理,最终将成为AOP增强对象。那么即便最初B获取A属性时,A尚未经过AOP处理,当A完成整个流程后,B中的A是否也会自动成为AOP增强对象?
答案是否定的,因为Spring 中对 A 做 AOP 是通过“生成一个新的代理对象”,而不是修改原始 A 的引用本身:
- B 中的 a 是早期注入进去的,它的类型是原始 A
- Spring 后续创建了 proxyA,它是一个新对象,代理了原始 A
- 但此时 B 中早就注入好了,不会“回头替换”那个引用
- 所以 B 中的 A 就是原始对象,没有事务、没有切面
一个简单案例即可证明:
public class MyService {public void doSomething() {System.out.println("Doing something...");}
}
@Aspect
public class LogAspect {@Before("execution(* MyService.*(..))")public void before() {System.out.println(">>> [AOP] Before method execution");}
}
public class AopTest {public static void main(String[] args) {MyService target = new MyService();// 创建代理工厂并添加切面AspectJProxyFactory factory = new AspectJProxyFactory(target);factory.addAspect(new LogAspect());// 获取代理对象MyService proxy = factory.getProxy();System.out.println("target = " + target.getClass());System.out.println("proxy = " + proxy.getClass());System.out.println("是否同一引用: " + (target == proxy));proxy.doSomething();}
}