聊聊Spring循环依赖三级缓存是否可以减少为二级缓存的情况
基于Spring-5.1.5.RELEASE
问题
都知道Spring通过三级缓存来解决循环依赖的问题。但是是不是必须三级缓存才能解决,二级缓存不能解决吗?
要分析是不是可以去掉其中一级缓存,就先过一遍Spring是如何通过三级缓存来解决循环依赖的。
循环依赖
所谓的循环依赖,就是两个或则两个以上的bean互相依赖对方,最终形成闭环。比如“A对象依赖B对象,而B对象也依赖A对象”,或者“A对象依赖B对象,B对象依赖C对象,C对象依赖A对象”;类似以下代码:
publicclassA{ privateBb; } publicclassB{ privateAa; }
常规情况下,会出现以下情况:
通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。
A对象需要注入B对象,发现对象池(缓存)里还没有B对象(对象在创建并且注入属性和初始化完成之后,会放入对象缓存里)。
通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。
B对象需要注入A对象,发现对象池里还没有A对象。
创建A对象,循环以上步骤。
三级缓存
Spring解决循环依赖的核心思想在于提前曝光:
通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。
A对象需要注入B对象,发现缓存里还没有B对象,将半成品对象A放入半成品缓存。
通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。
B对象需要注入A对象,从半成品缓存里取到半成品对象A。
B对象继续注入其他属性和初始化,之后将完成品B对象放入完成品缓存。
A对象继续注入属性,从完成品缓存中取到完成品B对象并注入。
A对象继续注入其他属性和初始化,之后将完成品A对象放入完成品缓存。
其中缓存有三级:
/**Cacheofsingletonobjects:beannametobeaninstance.*/ privatefinalMapsingletonObjects=newConcurrentHashMap<>(256); /**Cacheofearlysingletonobjects:beannametobeaninstance.*/ privatefinalMap earlySingletonObjects=newHashMap<>(16); /**Cacheofsingletonfactories:beannametoObjectFactory.*/ privatefinalMap >singletonFactories=newHashMap<>(16);
缓存 | 说明 |
---|---|
singletonObjects | 第一级缓存,存放可用的成品Bean。 |
earlySingletonObjects | 第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖。 |
singletonFactories | 第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。用以解决循环依赖。 |
要了解原理,最好的方法就是阅读源码,从创建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手。
1.在构造Bean对象之后,将对象提前曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性和初始化。
publicabstractclassAbstractAutowireCapableBeanFactoryextendsAbstractBeanFactory implementsAutowireCapableBeanFactory{ protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,final@NullableObject[]args) throwsBeanCreationException{ …… //是否提前曝光 booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&& isSingletonCurrentlyInCreation(beanName)); if(earlySingletonExposure){ if(logger.isTraceEnabled()){ logger.trace("Eagerlycachingbean'"+beanName+ "'toallowforresolvingpotentialcircularreferences"); } addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean)); } …… } }
2.提前曝光的对象被放入Map
publicclassDefaultSingletonBeanRegistryextendsSimpleAliasRegistryimplementsSingletonBeanRegistry{ protectedvoidaddSingletonFactory(StringbeanName,ObjectFactory>singletonFactory){ Assert.notNull(singletonFactory,"Singletonfactorymustnotbenull"); synchronized(this.singletonObjects){ //一级缓存 if(!this.singletonObjects.containsKey(beanName)){ //三级缓存 this.singletonFactories.put(beanName,singletonFactory); //二级缓存 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } } publicinterfaceObjectFactory{ TgetObject()throwsBeansException; }
3.为什么要包装一层ObjectFactory对象?
如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:
不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。
Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map
addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));:
publicabstractclassAbstractAutowireCapableBeanFactoryextendsAbstractBeanFactory implementsAutowireCapableBeanFactory{ protectedObjectgetEarlyBeanReference(StringbeanName,RootBeanDefinitionmbd,Objectbean){ ObjectexposedObject=bean; if(!mbd.isSynthetic()&&hasInstantiationAwareBeanPostProcessors()){ for(BeanPostProcessorbp:getBeanPostProcessors()){ if(bpinstanceofSmartInstantiationAwareBeanPostProcessor){ SmartInstantiationAwareBeanPostProcessoribp=(SmartInstantiationAwareBeanPostProcessor)bp; exposedObject=ibp.getEarlyBeanReference(exposedObject,beanName); } } } returnexposedObject; } }
为了防止对象在后面的初始化(init)时重复代理,在创建代理时,earlyProxyReferences缓存会记录已代理的对象。
publicabstractclassAbstractAutoProxyCreatorextendsProxyProcessorSupport implementsSmartInstantiationAwareBeanPostProcessor,BeanFactoryAware{ privatefinalMap
4.注入属性和初始化
提前曝光之后:
通过populateBean方法注入属性,在注入其他Bean对象时,会先去缓存里取,如果缓存没有,就创建该对象并注入。
通过initializeBean方法初始化对象,包含创建代理。
publicabstractclassAbstractAutowireCapableBeanFactoryextendsAbstractBeanFactory implementsAutowireCapableBeanFactory{ protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,final@NullableObject[]args) throwsBeanCreationException{ …… //Initializethebeaninstance. ObjectexposedObject=bean; try{ populateBean(beanName,mbd,instanceWrapper); exposedObject=initializeBean(beanName,exposedObject,mbd); } catch(Throwableex){ if(exinstanceofBeanCreationException&&beanName.equals(((BeanCreationException)ex).getBeanName())){ throw(BeanCreationException)ex; } else{ thrownewBeanCreationException( mbd.getResourceDescription(),beanName,"Initializationofbeanfailed",ex); } } …… } } //获取要注入的对象 publicclassDefaultSingletonBeanRegistryextendsSimpleAliasRegistryimplementsSingletonBeanRegistry{ protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ //一级缓存 ObjectsingletonObject=this.singletonObjects.get(beanName); if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){ synchronized(this.singletonObjects){ //二级缓存 singletonObject=this.earlySingletonObjects.get(beanName); if(singletonObject==null&&allowEarlyReference){ //三级缓存 ObjectFactory>singletonFactory=this.singletonFactories.get(beanName); if(singletonFactory!=null){ singletonObject=singletonFactory.getObject(); this.earlySingletonObjects.put(beanName,singletonObject); this.singletonFactories.remove(beanName); } } } } returnsingletonObject; } }
5.放入已完成创建的单例缓存
在经历了以下步骤之后,最终通过addSingleton方法将最终生成的可用的Bean放入到单例缓存里。
AbstractBeanFactory.doGetBean-> DefaultSingletonBeanRegistry.getSingleton-> AbstractAutowireCapableBeanFactory.createBean-> AbstractAutowireCapableBeanFactory.doCreateBean-> DefaultSingletonBeanRegistry.addSingleton publicclassDefaultSingletonBeanRegistryextendsSimpleAliasRegistryimplementsSingletonBeanRegistry{ /**Cacheofsingletonobjects:beannametobeaninstance.*/ privatefinalMapsingletonObjects=newConcurrentHashMap<>(256); /**Cacheofsingletonfactories:beannametoObjectFactory.*/ privatefinalMap >singletonFactories=newHashMap<>(16); /**Cacheofearlysingletonobjects:beannametobeaninstance.*/ privatefinalMap earlySingletonObjects=newHashMap<>(16); protectedvoidaddSingleton(StringbeanName,ObjectsingletonObject){ synchronized(this.singletonObjects){ this.singletonObjects.put(beanName,singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
二级缓存
上面第三步《为什么要包装一层ObjectFactory对象?》里讲到有两种选择:
不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。
Sping选择了第二种,如果是第一种,就会有以下不同的处理逻辑:
在提前曝光半成品时,直接执行getEarlyBeanReference创建到代理,并放入到缓存earlySingletonObjects中。
有了上一步,那就不需要通过ObjectFactory来延迟执行getEarlyBeanReference,也就不需要singletonFactories这一级缓存。
这种处理方式可行吗?
这里做个试验,对AbstractAutowireCapableBeanFactory做个小改造,在放入三级缓存之后立刻取出并放入二级缓存,这样三级缓存的作用就完全被忽略掉,就相当于只有二级缓存。
publicabstractclassAbstractAutowireCapableBeanFactoryextendsAbstractBeanFactory implementsAutowireCapableBeanFactory{ protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,final@NullableObject[]args) throwsBeanCreationException{ …… //是否提前曝光 booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&& isSingletonCurrentlyInCreation(beanName)); if(earlySingletonExposure){ if(logger.isTraceEnabled()){ logger.trace("Eagerlycachingbean'"+beanName+ "'toallowforresolvingpotentialcircularreferences"); } addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean)); //立刻从三级缓存取出放入二级缓存 getSingleton(beanName,true); } …… } }
测试结果是可以的,并且从源码上分析可以得出两种方式性能是一样的,并不会影响到Sping启动速度。那为什么Sping不选择二级缓存方式,而是要额外加一层缓存?
如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。
Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。
如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。