面试官:Spring解决循环依赖一定需要三级缓存吗?

1,072 阅读4分钟

我是大明哥,一个专注 [死磕 Java] 的硬核程序员。


回答

不一定需要!

Spring 三级缓存是解决循环依赖根本所在,但是,并不是说每种情况下解决循环依赖都必须使用三级缓存。对于无代理的单例 Bean,Spring 也可以仅需要二级缓存就可以解决循环依赖问题。

我们知道二级缓存是在创建实例后加入到缓存中的,如果要使用二级缓存来解决有代理场景下的循环依赖,我们只需要将 AOP 的代理工作提前到“提前暴露实例”的阶段执行就可以了,有就是说在创建实例阶段我们直接就创建代理对象,然后将代理对象放入到二级缓存中。但是这样设计的话就和 Spring AOP 的设计原则相驳:AOP 的实现需要与 bean 的正常生命周期的创建分离;按照 Spring AOP 的设计,代理对象的生成应该是在创建实例、属性填充、初始化之后再生成的。

所以,使用二级缓存是可以解决循环依赖,包括 AOP 的场景,但是,Spring 为了遵循 Spring AOP 的设计原则,采用了三级缓存来解决循环依赖。

详解

下面我们来看看,Spring 解决循环依赖一定需要三级缓存吗这个问题。我们需要先明确几个概念:

Spring 创建 Bean 的过程包括如下三个步骤:

已经初始化好的 Bean :是指已经完成创建 Bean 需要经历的三个阶段

未初始化好的 Bean :指只经过了 Bean 创建的第一个阶段,只将 bean 实例创建出来了

只使用一级缓存

只使用一级缓存可以解决循环依赖吗?我们知道一级缓存存放的是已经初始化好的 Bean 对象。过程见下图:

你会发现只使用一级缓存根本无法解决循环依赖的问题,主要原因就是一级缓存中存放的是已经初始化好的 Bean 对象,所以在 B 对象获取依赖属性 A 时,一级缓存中没有 A 对象,所以 B 无法完成属性填充,导致整个循环依赖链无法解决。

只使用二级缓存

二级缓存存放的未初始化好的 Bean 对象,过程见下图:

从这里可以看出,使用二级缓存其实就可以解决循环依赖了!

需要使用三级缓存

从上面可以看出,使用二级缓存是可以解决循环依赖的。但是这个情况是没有考虑代理对象的情况,在实际开发过程中,我们肯定会在一些使用 Spring AOP 的功能,比如实现自定义注解、使用 @Transaction ,使用注解实现缓存功能等等场景,这个时候注入的就不是原对象本身了,而是代理对象,那么对于这种场景使用二级缓存是否也可以解决循环依赖呢?

其实是可以的。我们知道二级缓存是在创建实例后加入到缓存中的,如果要使用二级缓存来解决有代理场景下的循环依赖,我们只需要将 AOP 的代理工作提前到“提前暴露实例”的阶段执行就可以了,有就是说在创建实例阶段我们直接就创建代理对象,然后将代理对象放入到二级缓存中。但是这样设计的话就和 Spring AOP 的设计原则相驳:AOP 的实现需要与 bean 的正常生命周期的创建分离;按照 Spring AOP 的设计,代理对象的生成应该是在创建实例、属性填充、初始化之后再生成的。

所以,使用二级缓存是可以解决循环依赖,包括 AOP 的场景,但是,Spring 为了遵循 Spring AOP 的设计原则,采用了三级缓存来解决循环依赖。

有位大佬通过改造 Spring 源码的方式,验证了二级缓存下,Spring 的确是可以完美解决循环依赖的,见下链接:zhuanlan.zhihu.com/p/496273636

更多

本文已收录到大明哥的「 Java 面试宝典」中了。

💻 死磕 Java