看过前两篇文章,并且自己跟过源码的同学, 应该对IOC创建bean的过程至少有了一个了解。
今天我们主要来看看Spring的三级缓存和循环依赖的解决。话不多说,咱们直接看看什么是三级缓存。
这是Spring容器getBean的时候,从缓存中获取bean的方法。这里3个缓存都已经被我圈出来,分别是以下3个map,且一级缓存是ConcurrentHashmap,当然是考虑线程安全。
那么,光这么看,好像也看不出什么名堂,我们要带着问题来看,这3个缓存分别在什么时候被用到,为什么需要3级缓存,等等。
首先,之前我们说过在bean在最初通过反射调用构造函数创建的时候,就被放入了三级缓存
从文章第一张图片可以看到,当我们从三级缓存中获取bean的时候,其实是拿到的是一个ObjectFacotory,而这里的匿名内部类的getEarlyBeanReference(得到提前暴露的bean的引用)就是ObjectFacotory.getObject的时候调用的方法(本文图一)。且,这里我们又看到了postProcessor的经典调用形式,现在我们应该很清楚这是干嘛的了吧?又是一个提供给我们做扩展,做修改的一个机会。之前又提过的postProcessor都是针对某个时机,可以做一些修改的。这里也一样,只不过时机换了。这里是已经创建出对象,但是并未完全实例化完成,是一个提前暴露的bean,如果我们项目中某个类自行实现了SmartInstantiationAwareBeanPostProcessor接口,那么就可以通过重写该接口的方法,来对该bean做一些修改。这就不纠结了,我相信大家对postProcessor的作用及使用,应该有所了解了。
那我们知道当单例bean创建完成之后,肯定是被放在一级缓存里,那二级缓存存在的意义是啥呢。有同学就会问,提前暴露的bean只需要放到三级缓存,实例化完成后,又是直接put到一级缓存。而我们看到文章图一从三级缓存获取bean之后,会升级到二级缓存,这里为什么不直接升级到一级缓存,为什么需要二级缓存呢。
这就是咱老生常谈的问题,为什么需要三级缓存,二级缓存不就行了。这里,我先给出总结,很多人都说,三级缓存是为了解决循环依赖,我其实并不赞同,因为二级缓存确实够了。当我们看到spring从缓存中获取bean
的时候,从二级缓存拿到的和三级缓存拿到的bean,唯一的区别是什么?就是在从三级缓存中拿取的时候,多了一次postProcessor接口的调用,即如果没有该接口的实现,那么这2个缓存获取的bean是一模一样的。对吧?还有的区别就是,从三级缓存获取bean,每一次都要遍历postProcessor接口,去做调用。性能上和直接从二级缓存获取就差了很多。所以,当有人问我为什么spring需要三级缓存的时候,我会这么回答,一方面是提供了一个postProcessor接口给我们一次机会在bean实例化完成前去对bean做修改,二是性能上会有提升。而并不是为了解决循环依赖。说到这儿肯定还有同学有点懵。那么我们先来说说,Spring是怎么解决循环依赖的。
先假设,class A 依赖 属性B class B 依赖 属性A
创建bean A的时候是先创建实例,设置该bean为提前暴露的bean放入三级缓存。再依赖注入B,在依赖注入的时候,会触发beanFactory.getBean(B)。这个时候就会先创建bean B,反射构造函数创建出B的bean之后,又依赖注入A,这个时候,又触发了beanFactory.getBean(A),这个时候会先从缓存中去找,会在三级缓存中找到有A这个bean,但是这个bean是还未完成依赖注入的。但是不妨碍我们拿到这个bean。因为是单例,所以内存中的地址是一样的。所以B会正常的完成实例化并放入一级缓存。这个时候B实例化完成了,再继续A的实例化,这个时候容器中已经有B的实例,A就也能正常的实例化完成了。
这就是循环依赖的整个过程,我们这么看,确实二级缓存就够了。
所以,三级缓存存在的意义,并不是为了解决循环依赖。只能说Spring的循环依赖,用到了三级缓存。而之所以用了三级缓存,是为了提升性能,和提升扩展性。
当然,以上是我个人的理解,如果有不对的地方,欢迎指正评论私信探讨。
那么Spring三级缓存和循环依赖,我们就讲到这儿了。