自从笔者参加校招开始背Java相关八股以来,就被灌输一个经典问题——spring解决循环依赖之三级缓存的必要性,没参加工作之前,一直以为所谓源码是“凡人不可触摸之物”,看到网上诸多对该问题的解答,不明觉厉,便并当作真理进行死记硬背。参加工作后,发现源码无非就是从github上clone下来就拿到了,而且笔者发现其实网上也有一小撮人说二级缓存可以,带着这个疑问笔者把spring源码拉了下来,并着重针对第三级缓存存在必要性,即“由于代理的需要”进行测试
源码版本5.3.8,AspectJ织入器版本org.aspectj:aspectjweaver:1.9.7
- 前提:移除第三级缓存——singletonFactories
- 这里是判断是否开启允许循环依赖存在的语句中,这里没有涉及源码改动,而是说要改addSingletonFactory方法内部逻辑
- addSingletonFactory方法内部逻辑改动为移除放入三级缓存,而是直接在这一步生成提前暴露的bean,也就是说singletonFactory.getObject中的逻辑,即返回原始bean或者生成代理对象的逻辑将在这就立马执行。
- 在从三级缓存中获取bean的地方移除第三级缓存,因为通过上述修改,肯定是可以能从二级缓存拿到了
- 验证
因此,我给出的答案是二级缓存是可以解决循环依赖的,想研究的同学可以自己debug下AbstractApplicationContext的refresh全流程细节,关于提前生成代理bean,会使得后面走到正常流程生成代理bean的时候会重复生成该代理bean也是不会的,因为spring-aop也有在打配合,即该模块也会有个缓存在记录提前暴露的代理对象(earlyProxyReferences),如果判断有的话,就不会再次生成代理了。那么为什么spring做了一件看似无意义的事情呢。笔者的推测是:spring是一个面向bean的产品,那么bean的生命周期,理应是目标代理bean,即原始bean实例化、依赖注入后得到一个完整体再去生成代理对象,将原始对象组合进代理对是合理的,这也是spring的bean生成流程。而循环依赖本身是一个工程上的错误,如果为了修复一个用户使用的错误,而去改变该产品的初衷流程就有点离谱了,因此spring选择的是打补丁的做法,即给用户一个选项,你可以通过设置(allow-circular-references)来允许循环依赖存在,我也可以给你尝试解决(因为在某些情况是无法解决的)。那么就算允许了循环依赖产生,如果spring选择用二级缓存来解决,就会导致即使没有循环依赖的bean也会提前生成代理对象(如果需要代理的话),就也丧失了产品的初衷流程了,那么合理的做法是,再加一个三级缓存,如果用户开启了允许循环依赖,那么不存在循环依赖的bean,仍然保持常规的bean生成流程,存在循环依赖的bean则提前生成代理对象(如果需要代理的话),这就是三级缓存存在的意义。
一些在这个问题上写的比较好的文章: 关于Spring的两三事:再谈三级缓存