《Spring源码最难问题:当Spring AOP遇上循环依赖.docx》由会员分享,可在线阅读,更多相关《Spring源码最难问题:当Spring AOP遇上循环依赖.docx(13页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Spring源码最难问题:当 Spring AOP 遇上循环依赖 前a问:Spring如何解决循环依赖?答:Spring通过提前曝光机制,利用三级缓存解决循环 依赖。再问:Spring通过提前曝光,直接曝光到二级缓存已经 可以解决循环依赖问题了,为什么一定要三级缓存?再细问:如果循环依赖的时候,所有类又都需要Spring AOP自动代理,那Spring如何提前曝光?曝光的是原始 bean还是代理后的bean?这些问题算是Spring源码的压轴题了,如果这些问题都 弄明白,恭喜你顺利结业Spring源码了。就单单对Spring 这一块的理解,不夸张的说可以到达阿里水准了源码分析/小* 4.填充属
2、性*如果Autowired注解属性,那么在上方完成解析后,在这里完成注入Autowiredrivate Inner inner;populateBean(beanName, mbd, instanceWrapper);/5.初始化exposedObject = initializeBean(beanName, exposedObject, mbd);|ch (Throwable ex) il (ex nstnnceof BeanCreationException & beanName.equals(BeanCr|ua(i()iil-.ccplion c i.LclI-kuii.Wimci i)
3、throw (BeanCreationException) ex;|else throw no BeanCreationException(mbd.getResourceDescription。,beanName, Initialization of bean failed, ex);/6.存在提前曝光情况下Object carlySinglctonRcfcrcncc 二 gctSinglcton(bcanNamc, | (c;iSin9h、l()nRclccnic I 7| i 八pi)、cd( )h|ci l hc;in i 1 腐螫够,:| cxp()、cd()bjccl C”L Sin
4、glclonR匕k| else if (!this.allowRawInjectionDespiteWrapping & hasDependentBean(| Siring dependentBeans 二 ge【DependentBeans(beanNanie | SetString actualDependenlBeans = nc LinkedHashSet(dependent 卜 m -, m 隙; | (Siring dependenlBeiin : dcpendenlBenn、)| if (!removeSingletonIfCreatedForTypeCheckOnIy(depe
5、ndentB6an)actual DependentBeans.add(dependentBean);/被依赖检测异|(匕 c I u ” D c p c n d c n B c a n、. i s IS m p l y ( )J,卷懿|throw new B canC u rrc n U y 1 nCcat iu n Ex ccpt i on (bean N a me| Bean with name + beanName + has been injected into other be| StringUtils.coHectionToCommaDelimitedString(actualD
6、ependentBe| in its raw version as part of a circular reference, but has eventualwrapped. This means that said other beans do not use the final versi“bean. This is often the result of over-eager type nialchintj - consideetBeanNamesOfType with the allowEagerlnit flag turned off, fo|这个时候我们需要理清一下3个变量ear
7、lySingletonReference:二级缓存,缓存的是经过提前曝光提前AOP代理的beanbean:这个就是经过了实例化、填充、初始化的beanexposedObject : 这个是经过了 AbstractAutoProxyCreator 的 postProcessAfterlnitialization 处理 过后的bean,但是在其中因为发现当前bean已经被 earlyProxyReferences缓存,所以并没有进行AOP处理, 而是直接跳过,因此还是跟第2点一样的bean 理清这3个变量以后,就会发现,exposedObject = earlySingletonReference
8、;AOP代理过的Bean赋值给了 exposedObject并返回,这时 候用户拿到的bean就是AOP代理过后的bean 了,一切 皆大欢喜了。但是中间还有一个问题!提前曝光的bean在提前引用时 被Spring AOP代理了,但是止匕时的bean只是经过了实例 化的bean,还没有进行Autowire的注入啊!也就是说此 时代理的bean里面自动注入的属性是空的!另外,搜索 公众号Linux就该这样学后台回复“猴子”,获取一份惊喜 礼包。(四)提前AOP代理对象的属性填充、初始化是的,确实在Spring AOP提前代理后没有经过属性填充 和初始化。那么这个代理又是如何保证依赖属性的注入的呢
9、?答案回到Spring AOP最早最早讲的JDK动态代理 上找,JDK动态代理时,会将目标对象target保存在最 后生成的代理$proxy中,当调用$proxy方法时会回调 h.invoke,而h.invoke又会回调目标对象target的原始方 法。因此,其实在Spring AOP动态代理时,原始bean已经被 保存在提前曝光代理中了。而后原始Bean继续完成属性 填充和初始化操作。因为AOP代理$proxy中保存着traget 也就是是原始bean的引用,因此后续原始bean的完善, 也就相当于Spring AOP中的target的完善,这样就保证 了 Spring AOP的属性填充与初
10、始化了!(五)循环依赖遇上Spring AOP图解为了帮助大家理解,这里灵魂画手画张流程图帮助大家 理解首先又bean A, bean B,他们循环依赖注入,同时bean A 还需要被Spring AOP代理,例如事务管理或者日志之类 的操作。原始bean A, bean B图中用a, b表示,而代理 后的bean A我们用aop.a表不。1,袁道从各圾播存中我取beangetSlnglGton(Ba)7翟试从各圾媚存中技取beangetSingletonCb)13.言词2.检冽a是否在创立状态crtlnCreate.contains(a)8.检测b是否在创立状态crtlnCreate.con
11、tainsCb)此时在第的a的工 aFac3.创立前,先将a标记为创立中crtlnCreate.add( a)9创立前,先将b标记为创立中crtlnCreate.addCb)4.实例化bean = createBeanlnstanceCa)10.实例化bean = createBeanlnstanceCb)5.将bean保存到三级缓存中addSinqletonFactoryl 带 a 的工厂);11.将bean保存到三级缓存中addSingtetonFactory(带 b 的工厂);b填充的属性是aop-a而非a6.填充属性发现需变引用BgotBoan(b)12.塌充属性发现需要引用AgotB
12、oan(-a)19.初始化17.初始化21.创立完成,标识a不在创立中 crtlnCreate.removeCa)怕.创立完成,标识b不在创立中 crtlnCreate.remove(b)AB+ b:B+a : AcurrentlylnCreation:正在创立的bean集合;下方简称crtl, ;昂+getA什earlySingletonObjects:二级缓存,缓存提前曝光的实例;下工 setB(B b);4setA(Aa);singletonFactories:三短缓存,缓存提前曝光的工厂,用于生产二级名getBean(b)getBean(b)getBeanCa)看完也算是Spring的
13、顺利结业了 !进入正题,在Spring创立Bean的核心代码doGetBean中, 在实例化bean之前,会先尝试从三级缓存获取bean,这 也是Spring解决循环依赖的开始(一)缓存中获取bean/ AbstractBeanFactory.iava T doGetBean。inal String name, Nullable lina ClassT rNullable inal Object args, boolean typeCheckOnly) throws BeansExString beanName = transfoimedBeanName(name);Object bean;/
14、 2.尝试从缓存中获取beanObject sharcdlnstance = getSingleton(beanName);rotcctcd Object getSingleton(String beanName, boolean allowEarlyRefere nee) 从一级缓存获取,key=beanName value=beanObject singletonObject = this.singletonObiects.get(beanName);I (singletonObject = nu & isSingletonCurrentlylnCreation(beanName)sing
15、lclon Object 二.carl ySinglctonObjccts.gct( bean Name );BMHBBMH(singlclonObjccl 二二 & zillovalyRcfcrcncc) * 三级缓存获取,key-beanName value=objectFactory, objectFactory 中* SmartlnstantiationAwareBeanPostProcessor 的 getEarlyBeanReferenceObjectFactoryv? singletonFactory = Uiis.singletonFactories.get(beanNam|3
16、S(singletonFactory != null) * 它 还经过 了 SmartlnstantiationAwareBeanPostProcessor 的也正)置处 匚提前singletonObjecl = singlelonFaclory.getObjecMthis.earlySingletonObjects.put(beanName, singletonObject);.singlulonl iicl()rius.rciiio ci 鼠二;uncrcluri singletonObject;三级缓存分别是:singletonObject : 一级缓存,该缓存 key = beanNa
17、me, value = bean;这里的bean是已经创立完成的, 该bean经历过实例化,属性填充,初始化以及各类的 后置处理。因此,一旦需要获取bean时,我们第一时 间就会寻找一级缓存earlySingletonObjects:二级缓存,该缓存 key = beanName, value = bean;这里跟一b级缓存的区别在于,该 缓存所获取到的bean是提前曝光出来的,是还没创立 完成的。也就是说获取到的bean只能确保已经进行了 实例化,但是属性填充跟初始化肯定还没有做完,因 此该bean还没创立完成,仅仅能作为指针提前曝光, 被其他bean所引用singletonFactorie
18、s : 三级缓存,该缓存 key = beanName, value = beanFactory;在 bean 实例化完之后,属 性填充以及初始化之前,如果允许提前曝光,spring 会将实例化后的bean提前曝光,也就是把该bean转 换成beanFactory并加入到三级缓存。在需要引用提前 曝光对象时再通过singletonFactory.getObject()获取。这里抛出问题,如果我们直接将提前曝光的对象放到二 级缓存earlySingletonObjects, Spring循环依赖时直接取就 可以解决循环依赖了,为什么还要三级缓存 singletonFactory然后再通过getO
19、bject()来获取呢?这不是 多此一举?(二)三级缓存的添加我们回到添加三级缓存,添加SingletonFaclory的地方,看 看gelObjectO到底做了什么操作reti” n this.getEarlyBeanRefcrence(beanName, mbd, bean);tn可以看到在返回getObject()时,多做了 一步 getEarlyBeanReference 操作,这步操作是 BeanPostProcess 的 一种,也就是给子类重写的一个后处理器,目的是用于 被提前引用时进行拓展。另外,搜索公众号后端架构师 后台回复“物联网”,获取一份惊喜礼包。即:曝光的时候并不调用该
20、后置处理器,只有曝光,且 被提前引用的时候才调用,确保了被提前引用这个时机 触发。(三)提前曝光代理 earlyProxyReferences因此所有的重点都落到了 getEarlyBeanReference上, getEarlyBeanReference方法是Smartinstantiation A wareBeanPostProcessor 所规定的接口。再 通过 UML的类图查看实现类,仅有 Abstract AutoProxyCreator进行了实现。也就是说,除了用户 在子类重写,否那么仅有AbstractAutoProxyCreator一种情况/ AbstractAutoProxy
21、Creator.iavpublic Object getEarlyBeanReference(Object bean, Siring beanNamc) Object cacheKcy = gctCachcKey(bcan.getClass(), beanName);. cnrlP()yRckrcncc、.pul(cacheKc” been 1憾w rap! INeccssai yi bean. bccuiXanic, cachcKc)wrapIfNecessary 是用于 Spring AOP 自动代理的。Spring 将 当前bean缓存到earlyProxyReferences中标识提前
22、曝光的 bean在被提前引用之前,然后进行了 Spring AOP代理。牛逼啊!接私活必备的N个开源工程!赶快收藏吧但是经过Spring AOP代理后的bean就已经不再是原来的 bean 了,经过代理后的bean是一个全新的bean,也就是 说代理前后的2个bean连内存地址都不一样了。这时将再引出新的问题:B提前引用A将引用到A的代 理,这是符合常理的,但是最原始的bean A在B完成创 建后将继续创立,那么Spring loc最后返回的Bean是 Bean A呢还是经过代理后的Bean呢?这个问题我们得回到Spring AOP代理,Spring AOP代理 时机有2个:当自定义了 Tar
23、getSource,那么在bean实例化前完 成Spring AOP代理并且直接发生短路操作,返回bean正常情况下,都是在bean初始化后进行Spring AOP代理如果要加上今天说的提前曝光代理,getEarlyBeanReference 可以说3种第一种情况就没什么好探究的了,直接短路了,根本没 有后续操作。而我们关心的是第二种情况,在Spring初 始化后置处理器中发生的Spring AOP代理Ipuhlic Object applyBeanPostProcessorsAfterInitialization(Objcct existingRin.、I11;i八;in;一、-111、川(
24、)h |、门 i c、u 11 八 71 n 口 卜;c n i:for (BcanPostProccssor processor : gclBcanPoslProccssorsQ) Object current = processor.postProcessAfterlnitialization(result, beanName);result = current;!rctu i n result;/ AbstractAutoProxyCreator.iavapublic Object postProcessAfterInitialization(Nullablc Object bean,
25、Strin|Objccl cachcKcy = gclCachcKcy(bcan*ctClassQ, bcanNanic)、,|( .carlyProxyRcfcrcnccs.rcmoc(cachcKcy) !二 bean) 1 rapl iNcccssarx( lum. bean a me. cn C v):retn rn bean;earlyProxyReferences是不是有点熟悉,是的,这就是我们 刚刚提前曝光并且进行Spring AOP提前代理时缓存的原 始bean,如果缓存的原始bean跟当前的bean是一至的, 那么就不进行Spring AOP代理了!返回原始的bean。扩 展:接私活儿protected Object doCreateBean(final String bcanName, fin;i RootBeanDefinilion nihd. (NuHnhlc()卜心1 |throws BeanCreationException