Spring?循環(huán)依賴之AOP實(shí)現(xiàn)詳情
前言:
我們接著上一篇文章繼續(xù)往下看,首先看一下下面的例子,前面的兩個(gè)serviceA和serviceB不變,我們添加一個(gè)BeanPostProcessor
:
@Component public class MyPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("serviceA")){ System.out.println("create new ServiceA"); return new ServiceA(); } return bean; } }
運(yùn)行一下,結(jié)果報(bào)錯(cuò)了:
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'serviceA': Bean with name 'serviceA'
has been injected into other beans [serviceB] 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.
在分析錯(cuò)誤之前,我們再梳理一下正常循環(huán)依賴的過程:
- 1、初始化原生對象serviceA,放入三級緩存
- 2、serviceA填充屬性,發(fā)現(xiàn)依賴serviceB,創(chuàng)建依賴對象
- 3、創(chuàng)建serviceB,填充屬性發(fā)現(xiàn)依賴serviceA,從三級緩存中找到填充
- 4、執(zhí)行serviceB的后置處理器和回調(diào)方法,放入單例池
- 5、執(zhí)行serviceA的后置處理器和回調(diào)方法,放入單例池
再回頭看上面的錯(cuò)誤,大意為在循環(huán)依賴中我們給serviceB注入了serviceA,但是注入之后我們又在后置處理器中對serviceA進(jìn)行了包裝,因此導(dǎo)致了serviceB中注入的和最后生成的serviceA不一致。
但是熟悉aop的同學(xué)應(yīng)該知道,aop的底層也是利用后置處理器實(shí)現(xiàn)的啊,那么為什么aop就可以正常執(zhí)行呢?我們添加一個(gè)切面橫切serviceA的getServiceB
方法:
@Component @Aspect public class MyAspect { @Around("execution(* com.hydra.service.ServiceA.getServiceB())") public void invoke(ProceedingJoinPoint pjp){ try{ System.out.println("execute aop around method"); pjp.proceed(); }catch (Throwable e){ e.printStackTrace(); } } }
先不看運(yùn)行結(jié)果,代碼可以正常執(zhí)行不出現(xiàn)異常,那么aop是怎么實(shí)現(xiàn)的呢?
前面的流程和不使用aop相同,我們運(yùn)行到serviceB需要注入serviceA的地方,調(diào)用getSingleton
方法從三級緩存中獲取serviceA存儲的singletonFactory
,調(diào)用getEarlyBeanReference
方法。在該方法中遍歷執(zhí)行SmartInstantiationAwareBeanPostProcessor
后置處理器的getEarlyBeanReference
方法:
看一下都有哪些類實(shí)現(xiàn)了這個(gè)方法:
在spring中,就是這個(gè)AbstractAutoProxyCreator
負(fù)責(zé)實(shí)現(xiàn)了aop,進(jìn)入getEarlyBeanReference
方法:
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { //beanName Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); //產(chǎn)生代理對象 return wrapIfNecessary(bean, beanName, cacheKey); }
earlyProxyReferences
是一個(gè)Map,用于緩存bean的原始對象,也就是執(zhí)行aop之前的bean,非常重要,在后面還會用到這個(gè)Map:
Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
記住下面這個(gè)wrapIfNecessary
方法,它才是真正負(fù)責(zé)生成代理對象的方法:
上面首先解析并拿到所有的切面,調(diào)用createProxy
方法創(chuàng)建代理對象并返回。然后回到getSingleton
方法中,將serviceA加入二級緩存,并從三級緩存中移除掉。
可以看到,二級緩存中的serviceA已經(jīng)是被cglib代理過的代理對象了,當(dāng)然這時(shí)的serviceA還是沒有屬性值填充的。
那么這里又會有一個(gè)問題,我們之前講過,在填充完屬性后,會調(diào)用后置處理器中的方法,而這些方法都是基于原始對象的,而不是代理對象。
在前一篇文章中我們也講過,在initializeBean
方法中會執(zhí)行后置處理器,并且正常情況下aop也是在這里完成的。那么我們就要面臨一個(gè)問題,如果避免重復(fù)執(zhí)行aop的過程。在initializeBean
方法中:
if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }
調(diào)用applyBeanPostProcessorsAfterInitialization
,執(zhí)行所有后置處理器的after
方法:
執(zhí)行AbstractAutoProxyCreator
的postProcessAfterInitialization
方法:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
earlyProxyReferences
我們之前說過非常重要,它緩存了進(jìn)行aop之前的原始對象,并且這里參數(shù)傳入的Object也是原始對象。因此在這里執(zhí)行remove
操作的判斷語句返回false
,不會執(zhí)行if中的語句,不會再執(zhí)行一遍aop的過程。
回過頭來再梳理一下,因?yàn)橹斑M(jìn)行過循環(huán)依賴,所以提前執(zhí)行了AbstractAutoProxyCreator
的getEarlyBeanReference
方法,執(zhí)行了aop的過程,在earlyProxyReferences
中緩存了原生對象。因此在循環(huán)依賴的情況下,等式成立,直接返回。而在沒有循環(huán)依賴的普通情況下,earlyProxyReferences
執(zhí)行remove
返回為null
,等式不成立,正常執(zhí)行aop流程。
需要注意的是,這個(gè)方法中最終返回的還是原始對象,而不是aop后的代理對象。執(zhí)行到這一步,我們先看一下嵌套的狀態(tài):
對外暴露的serviceA是原始對象,依賴的serviceB已經(jīng)被注入了。而serviceB中依賴的serviceA是代理對象,并且這個(gè)代理對象依賴的serviceB還沒有被注入。
向下執(zhí)行:
再次通過getSingleton
獲取serviceA:
這次我們通過二級緩存就可以拿到之前經(jīng)過aop的代理對象,因此不用找三級緩存直接返回這個(gè)代理對象,并最終把這個(gè)代理對象添加到一級緩存單例池中。
到這,我們對三級緩存的作用做一個(gè)總結(jié):
- 1、
singletonObjects
:單例池,緩存了經(jīng)過完整生命周期的bean - 2、
earlySingletonObjects
:緩存了提前曝光的原始對象,注意這里存的還不是bean,這里存的對象經(jīng)過了aop的代理,但是沒有執(zhí)行屬性的填充以及后置處理器方法的執(zhí)行 - 3、
singletonFactories
:緩存的是ObjectFactory
,主要用來去生成原始對象進(jìn)行了aop之后得到的代理對象。在每個(gè)bean的生成過程中,都會提前在這里緩存一個(gè)工廠。如果沒有出現(xiàn)循環(huán)依賴依賴這個(gè)bean,那么這個(gè)工廠不會起到作用,按照正常生命周期執(zhí)行,執(zhí)行完后直接把本bean放入一級緩存中。如果出現(xiàn)了循環(huán)依賴依賴了這個(gè)bean,沒有aop的情況下直接返回原始對象,有aop的情況下返回代理對象。
全部創(chuàng)建流程結(jié)束,看一下結(jié)果:
我們發(fā)現(xiàn),在生成的serviceA的cglib
代理對象中,serviceB屬性值并沒有被填充,只有serviceB中serviceA的屬性填充成功了。
可以看到如果使用cglib,在代理對象的target
中會包裹一個(gè)原始對象,而原始對象的屬性是被填充過的。
那么,如果不使用cglib代理,而使用jdk動(dòng)態(tài)代理呢?我們對之前的代碼進(jìn)行一下改造,添加兩個(gè)接口:
public interface IServiceA { public IServiceB getServiceB(); } public interface IServiceB { public IServiceA getServiceA(); }
改造兩個(gè)Service類:
@Component public class ServiceA implements IServiceA{ @Autowired private IServiceB serviceB; public IServiceB getServiceB() { System.out.println("get ServiceB"); return this.serviceB; } } @Component public class ServiceB implements IServiceB{ @Autowired private IServiceA serviceA; public IServiceA getServiceA() { System.out.println("get ServiceA"); return serviceA; } }
執(zhí)行結(jié)果:
看一下serviceA的詳細(xì)信息:
同樣也是在target
中包裹了原生對象,并在原生對象中注入了serviceB的實(shí)例。
綜上兩種方法,可以看出在我們執(zhí)行serviceA的getServiceB方法時(shí),都無法正常獲取到其bean對象,都會返回一個(gè)null
值。那么如果非要直接獲得這個(gè)serviceB應(yīng)該怎么辦呢?
我們可以通過反射的方式,先看cglib代理情況下:
ServiceA serviceA= (ServiceA) context.getBean("serviceA"); Field h = serviceA.getClass().getDeclaredField("CGLIB$CALLBACK_0"); h.setAccessible(true); Object dynamicAdvisedInterceptor = h.get(serviceA); Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); advised.setAccessible(true); Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget(); ServiceA serviceA1= (ServiceA) target; System.out.println(serviceA1.getServiceB());
再看看jdk動(dòng)態(tài)代理情況下:
IServiceA serviceA = (IServiceA) context.getBean("serviceA"); Field h=serviceA.getClass().getSuperclass().getDeclaredField("h"); h.setAccessible(true); AopProxy aopProxy = (AopProxy) h.get(serviceA); Field advised = aopProxy.getClass().getDeclaredField("advised"); advised.setAccessible(true); Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget(); ServiceA serviceA1= (ServiceA) target; System.out.println(serviceA1.getServiceB());
執(zhí)行結(jié)果都能獲取到serviceB的實(shí)例:
對aop情況下的循環(huán)依賴進(jìn)行一下總結(jié):spring專門為了處理aop情況下的循環(huán)依賴提供了特殊的解決方案,但是不論是使用jdk動(dòng)態(tài)代理還是cglib代理,都在代理對象的內(nèi)部包裹了原始對象,在原始對象中才有依賴的屬性。此外,如果我們使用了后置處理器對bean進(jìn)行包裝,循環(huán)依賴的問題還是不能解決的。
總結(jié):
最后對本文的重點(diǎn)進(jìn)行一下總結(jié):
- 1、spring通過借助三級緩存完成了循環(huán)依賴的實(shí)現(xiàn),這個(gè)過程中要清楚三級緩存分別在什么場景下發(fā)揮了什么具體作用
- 2、產(chǎn)生aop情況下,調(diào)用后置處理器并將生成的代理對象提前曝光,并通過額外的一個(gè)緩存避免重復(fù)執(zhí)行aop
- 3、二級緩存和三級緩存只有在產(chǎn)生循環(huán)依賴的情況下,才會真正起到作用
- 4、此外,除去本文中提到的通過屬性的方式注入依賴的情況外,大家可能會好奇如果使用構(gòu)造函數(shù)能否實(shí)現(xiàn)循環(huán)依賴,結(jié)果是不可以的。具體的調(diào)用過程這里不再多說,有興趣的同學(xué)可以自己再對照源碼進(jìn)行一下梳理。
到此這篇關(guān)于Spring 循環(huán)依賴之AOP實(shí)現(xiàn)詳情的文章就介紹到這了,更多相關(guān)Spring 循環(huán)依賴 AOP 實(shí)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot?Security使用MySQL實(shí)現(xiàn)驗(yàn)證與權(quán)限管理
安全管理是軟件系統(tǒng)必不可少的的功能。根據(jù)經(jīng)典的“墨菲定律”——凡是可能,總會發(fā)生。如果系統(tǒng)存在安全隱患,最終必然會出現(xiàn)問題,這篇文章主要介紹了SpringBoot安全管理Spring?Security基本配置2022-11-11Java之while與do-while循環(huán)的用法詳解
在上一篇文章中,給大家講解了循環(huán)的概念,并重點(diǎn)給大家講解了for循環(huán)的使用。但在Java中,除了for循環(huán)之外,還有while、do-while、foreach等循環(huán)形式。這篇文章給大家講解while循環(huán)的使用2023-05-05java 中自定義OutputFormat的實(shí)例詳解
這篇文章主要介紹了java 中 自定義OutputFormat的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例幫助大家學(xué)習(xí)理解這部分內(nèi)容,希望通過本文能幫助到大家,需要的朋友可以參考下2017-08-08Token登陸驗(yàn)證機(jī)制的原理及實(shí)現(xiàn)
這篇文章介紹了Token登陸驗(yàn)證機(jī)制的原理及實(shí)現(xiàn),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12