三级缓存
注意,三级缓存这个叫法是国内的叫法,Spring官方没有任何三级缓存相关的叫法,这个三级缓存的叫法来源于三个成员变量:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
1 | swift复制代码/** Cache of singleton objects: bean name to bean instance. 一级缓存*/ |
个人整理了一些资料,有需要的朋友可以直接点击领取。
所以其实我们也应该从实际使用意义上去理解这3个cache的意义,而不是只在名字上做文章,毕竟这3个名字很不合理。
Spring生成Bean时如何利用三级缓存
Spring在创建Bean的过程我们简化成我们关心的步骤:
- 实例化Bean
- 判断作用域是否为单例,允许循环依赖,并且当前bean正在创建,还没有创建完成。如果都满足条件,则调用addSingletonFactory将bean实例放入三级缓存中,同时删除二级缓存中相同的beanName元素,这里我们发现了一个秘密,单例对象只可能存在于三级缓存中的某一个,所以在添加到某一缓存中时,一定会删除其他2级缓存中的相同对象
- 调用populateBean方法进行依赖注入
- 调用initializeBean方法完成对象初始化和AOP增强
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory,添加到三级缓存方法
1 | kotlin复制代码protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
其中涉及的流程可以从
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly)
这个方法开始,这个方法是个关键的方法,因为里面包含了创建Bean的流程,Spring在设计时,将createBean放到了getBean的流程里
说实话,这个方法太长,而且很多逻辑除非作者来讲,否则很难自己将每一句代码的意思都读懂,这里我们只挑感兴趣的流程来看,
第一步我们看到第8行代码,
急切的检查手动注册单例的单例缓存,eager我们通常解释为饿汉式,其实也有点乐观的意思,这里就直接到单例缓存中去取,如果取到那肯定就是单例的,没取到就需要创建。这种套路我们进行实际编程时应该学到,是个很常见的编程模式。
这里的逻辑是直接从单例池中获取,如果获取到了就可以直接返回bean,如果没有获取到就需要创建,在创建的逻辑里再去判断是singleton还是prototype。
这里可以看到26行抛出了一个异常,原因是这个bean是一个prototype的,而且发现这个bean已经被加入到了正在创建的bean队列中,只有一种可能就是,在创建他的时候,他被加入到正在创建队列中,他引用了别人,在创建别人时,别人引用了他,这种prototype的循环引用,Spring不提供解决方案,直接抛出异常,Spring希望这种情况下的循环引用由应用自己修改逻辑。
下面我们可以看到82、100、118行就是真正的实例化bean的地方,org.springframework.beans.factory.support.AbstractBeanFactory#createBean(),这个方法在AbstractBeanFactory中是抽象的空方法,交给了子类去实现,由于createBean中涉及了三级缓存相关的逻辑所以我们不得不在看看相关的源码。
1 | scss复制代码protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, |
如下就是createBean()方法的源码,老长了
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
很不幸,方法虽长,核心却被封装到了另一个方法doCreateBean()中,看看41行,我们又要去粘贴源码了…….
1 | java复制代码@Override |
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean()
看第10行,是实例化对象,可以理解为给对象分配内存
看第41行,就是加入第三级缓存,终于找到这个关键点了,注意这里有个非常重要的点,就是传递的是一个lambda表达式,也就是一个函数,也就是说三级缓存里面直接存储的是并不一个bean,想要从三级缓存里拿出bean,需要调用一下这个函数,调用时机是在getSingleton()函数触发的,也就是说这里只是关联上了方法,不会去调用。
看第47行,是填充属性的方法,这个方法里会触发引用对象的创建,包括循环依赖的对象的创建
看第48行,是后置处理逻辑的地方
1 | scss复制代码protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) |
这个就是属性装配的方法,里面涉及了引用对象的创建
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean()
看org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues()这个方法里面就知道。
1 | ini复制代码protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { |
下面就是将bean对应的ObjectFactory添加到三级缓存的代码:
1 | kotlin复制代码protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
这里需要注意的是,调用加入三级缓存的方法时,传递的实参是什么下面这个函数,相当于存入三级缓存保留了一段后置逻辑,等到调用getSingleton()是就会触发,为什么要这么设计呢?为什么不在存入的时候直接就执行了这段逻辑?
1 | ini复制代码protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { |
我们关心的是什么时候取出来,取出来的时机是getSingleton()方法,这里有2个方法:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
前者是用于检查单例池中是否有bean,同时会提升三级缓存到二级缓存,这么做的原因要回头看doGetBean()方法的逻辑,每一个doGetBean()的方法最前面都会调用第一个getSingleton()方法,一般而言,就2种情况,一级缓存中找到,或者一级缓存中没找到,三级缓存中找到,此时是循环依赖的情况,那么此时Spring就要提升依赖bean到二级缓存。后者用于createBean,因为第二个参数传递的是createBean()函数,更重要的是他在里面调用了,看第39行的singletonFactory.getObject()会触发createBean动作。然后后面有一个加入一级缓存的动作。
1 | kotlin复制代码protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
流程总结:
前提:我们将2个重载的getSingleton()方法依次命名为getSingleton1()和getSingleton2(),具体区别看上面讲解
在Spring中,将createBean的动作封装在了doGetBean()动作里,doGetBean()每次获取bean都要先从单例池中去拿单例:getSingleton1(),注意,getSingleton1()方法是有一些逻辑的,不仅仅是一个get动作,他有一个执行三级缓存中ObjectFactory生产对象的动作,这里有需要的话,会产生代理对象,并将三级缓存提升至二级缓存。当然,如果getSingleton1()方法返回的是null,doGetBean()将会调用getSingleton2(),那么将会触发doCreateBean()方法,doCreateBean()方法是singleton和prototype类型的bean通用的方法,doCreateBean()首先实例化bean,然后判断是否是singleton,是的话就加入到三级缓存,然后进行属性填充和初始化回调,然后根据条件调用getSintleton1(),同时接着getSingleton2()方法将会把创建的对象加入到1级缓存,注意这个顺序,就这样,完成对象的创建了。
动态代理+循环依赖
这里还有个更加重要场景,就是循环依赖+动态代理。这种场景在实际使用中是会产生的,如果仅仅是循环依赖或是仅仅是动态代理,这个很好理解。假设这里有A、B2个对象互相依赖且A需要生成代理对象,假设先实例化A,那么他们实例化的流程是:
实例化A,将A加入三级缓存,填充A属性,发现B
实例化B,将B加入三级缓存,填充B属性,发现A
从三级缓存中取到A,然后产生A的代理对象,并将A的代理对象升级到2级缓存,将B升级到1级缓存等,完成B的创建
将A升级到一级缓存等操作,完成A的创建
这段代码是上面org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean()中的一段,他存入三级缓存的这个函数,就包含了获取代理对象的逻辑。
1 | erlang复制代码if (earlySingletonExposure) { |
事实是动态代理+循环依赖一般情况下,Spring会报一个错误:
Error creating bean with name ‘classA’: Bean with name ‘classA’ has been injected into other beans [classB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.
如果加上一个懒加载就能解决问题。
单例懒加载
一般说懒加载都是说单例的,单纯的一个没有引用其他对象,也没有被其他对象引用的对象似乎比较好理解,就是扫描到对象了,判断一下是否是延迟加载,是的话就不进行实例化后后续初始化。这个好理解,主要是bean都是互相引用的,如果懒加载bean和非懒加载bean之间互相引用了,我们需要搞清楚现象。这里有个大胆的猜测:
1、懒加载bean引用非懒加载bean时,非懒加载bean照常进行初始化
2、非懒加载bean引用懒加载bean时,懒加载bean也要照常进行初始化
最后
感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!
本文转载自: 掘金