首先先回答几个疑问:
1.实际上代理类是相当于持有一个原对象(spring用的两种代理,Proxy和cglib都是一样):先创建对象,再创建代理类,再初始化原对象,和初始化之后再创建代理类,是一样的。
基于上述类写个main方法测试:
先根据空对象创建代理类,再初始化空对象,执行代理类方法,没问题!(忽略get/set方法)
2.尾部对象依赖前面对象,所以在尾部对象初始化时,就调用三级缓存中对象工厂的接口方法,即AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法根据前面的空对象创建代理类,并设值给尾部对象。
前面的对象在装配和初始化完成之后,spring通过这段代码,将二级缓存中的代理类取出返回,最后会设置到一级缓存中,从而保证尾部对象依赖的,和容器中的前面对象,是一个对象。
下面说下我的结论:只用两级缓存可以解决循环依赖,甚至一级缓存就行(AOP也同样适用)。
要理解下面的内容需要阅读过一定的spring源码,基础较弱的读者建议去看本回答的出处: 【超级干货】为什么spring一定要弄个三级缓存?。以免错过此次刷新你认知的机会(不是吹牛)。
不信你可以改下添加三级缓存的源码,直接加入第二层缓存或者第一层缓存里。
启动正常,所以验证了上述结论
为什么呢?很简单,解决循环依赖只需要保证创建完成的bean和创建中与设置到其他相互引用的bean里的bean是同一个就行。
没有代理的情况下(getEarlyBeanReference返回原对象)去创建AServiceImpl(简称AS):
有代理的情况下(getEarlyBeanReference返回代理对象)去创建AServiceImpl(给AServiceImpl和BServiceImpl都加上事务注解):
前面5步都没啥问题,只是第一层缓存里的是AS代理类(BS里的也是),第6步中,原AS对象初始化完成,则AS代理类其实也初始化完成,所以进行引用覆盖,返回缓存中的代理类即可。
从技术的角度看一层缓存就能解决循环依赖,为什么spring要整的这么复杂呢?
请大家换个角度,不要再站在spring使用者的角度去思考了,现在要假设自己是spring的开发者!
不要在别人已经既定设计好的方案里去猜人家当时为什么这么设计,这就是从结果反推过程,说实话很容易潜意识认为就应该这样设计,从而变成想方设法圆他人所说。
先抛却掉这几级缓存,重新审视下创建和初始化bean实例的代码。
一个bean在创建过程中可能会产生两个对象:
如果现在要对bean做增强,比如实现切面,则需要生成代理类,所以spring在上述两个方法中通过BeanPostProcessor类提供了拓展点。
假如我是spring这块代码的开发者,如果我这么设计(只用一层缓存):
假设spring没有提供AOP,需要使用spring的人自己去实现,那我就需要写个说明文档告诉他们:
相信这样的文档会很让使用者很困惑:
而且还暴露了很多内部设计细节,一个优秀的框架就是要让使用者对内部细节知道的越少越好,这样才便于迭代升级。
所以大家有没有发现,这种设计,让getEarlyBeanReference成为了创建bean时必会被调用的核心方法,而原本此处只是为了循环依赖时先给其他bean赋值!
倘若我们压根都没有循环依赖,bean本身的创建流程就应该是先new(一般是通过反射创建)一个,再装配初始化,最后放入实例缓存,此种设计就成了本末倒置!
那怎么调整呢?让getEarlyBeanReference延迟触发,只在有循环依赖时被引用的bean需要赋值当前bean时才触发!
所以就有了对象工厂ObjectFactory,也就有了第三级缓存。
而最理想的情况下,可以不用第二级缓存。
举个例子,A依赖B,B依赖A和C和D,C和D又依赖A,创建A的时候初始化需要B,创建B的时候初始化需要A,拿到A的ObjectFactory后调用接口方法获取对象,B还需要C和D,它们又需要去调用A的ObjectFactory,所以就重复调用了getObject方法,其实只要getEarlyBeanReference方法实现保证同一个beanName返回同一个对象,就不需要第二级缓存。
但是,这又暴露了内部实现细节,假如我弄个BeanPostProcessor实现类,spring也没有提示我要遵守上述约定,而我在getEarlyBeanReference方法里只是创建新对象返回,这就会导致B里面的是A1,C里面是A2,D里面是A3,那完了,芭比Q了!
所以让实现者去做重复性判断是不可控的,很容易出现问题,于是乎引入了第二级缓存,当调用三级缓存里的对象工厂的getObject方法之后,spring就会把返回值放入二级缓存,删除三级缓存,这样C和D取的就是二级缓存里的A对象,和B里的是同一个。
所以二级缓存和三级缓存其实是一套组合拳,不要拆成两个独立的东西去理解,出发点就不对。
基于这种设计,没有发生循环依赖的bean就是正常的创建流程,有相互引用的bean(除链尾的那个,比如之前的BS)会触发getEarlyBeanReference。
spring其实也不想你用框架前还要先了解循环引用,所以把getEarlyBeanReference方法设计在了SmartInstantiationAwareBeanPostProcessor接口中。从设计者这里的注释也能看出:此接口是一个专用接口,主要用于框架内的内部使用!
AOP是spring内部集成的,它的开发者知道这些逻辑,所以AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口,可以看看其对这两个方法的实现。
循环引用的bean的创建过程会触发这两个方法,所以在getEarlyBeanReference方法中会打标记再去判断是否需要创建代理类(wrapIfNecessary),而postProcessAfterInitialization方法则需要先判断标记,以免重复执行wrapIfNecessary。
也得亏根据空对象去创建代理类后再去初始化原对象,和根据已初始化后的对象创建代理类效果一样。
否则这里 getEarlyBeanReference方法就没啥用了,只能返回原对象,在postProcessAfterInitialization方法返回前加上把相互引用的其他bean的引用指向方法返回值的操作了,那就没现在这么简单了。
最后再对这三级缓存做个简单的总结: