A. spring為什麼要使用三級緩存解決循環依賴
首先清楚spring中bean 的載入過程:
1 解析需要spring管理的類為beanDefinition
2 通過反射實例化對象
3 反射設置屬性
4初始化,調用initMethod等。(postConstruct也是在這執行)
循環依賴的問題: a依賴b,b依賴a。
在a實例化之後會先將a放入到緩存中,然後給a設置屬性,去緩存中查到b。此時找不到就開始b的創建。b實例化之後,放入到緩存中,需要給a設置屬性,此時去緩存中查到a設置成功。然後初始化。成功後將b放入一級緩存。這個時候a在給自己屬性b設置值的時候就找到了b,然後設置b。完成屬性設置,再初始化,初始化後a放入一級緩存。
解決代理對象(如aop)循環依賴的問題。
例: a依賴b,b依賴a,同時a,b都被aop增強。
首先明確aop的實現是通過 postBeanProcess後置處理器,在初始化之後做代理操作的。
為什麼使用三級緩存原因:
1 只使用二級緩存,且二級緩存緩存的是一個不完整的bean
如果只使用二級緩存,且二級緩存緩存的是一個不完整的bean,這個時候a在設置屬性的過程中去獲取b(這個時候a還沒有被aop的後置處理器增強),創建b的過程中,b依賴a,b去緩存中拿a拿到的是沒有經過代理的a。就有問題。
2 使用二級緩存,且二級緩存是一個工廠方法的緩存
如果二級緩存是一個工廠的緩存,在從緩存中獲取的時候獲取到經過aop增強的對象。可以看到從工廠緩存中獲取的邏輯。
protected ObjectgetEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && ()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bpinstanceof ) {
ibp = () bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
a依賴b,b依賴a,c。c又依賴a。a,b,c均aop增強。
載入開始: a實例化,放入工廠緩存,設置b,b實例化,設置屬性,拿到a,此時從工廠緩存中拿到代理後的a。由於a沒載入完畢,不會放入一級緩存。這個時候b開始設置c,c實例化,設置屬性a,又去工廠緩存中拿對象a。這個時候拿到的a和b從工廠緩存不是一個對象。出現問題。
3 使用二級緩存,二級緩存緩存的是增強後的bean。這個與spring載入流程不符合。spring載入流程是:實例化,設置屬性,初始化,增強。在有循環引用的時候,之前的bean並不會增強後放入到二級緩存。
綜上1,2,3 可知二級緩存解決不了有aop的循環依賴。spring採用了三級緩存。
一級緩存 singletonObjects 緩存載入完成的bean。
二級緩存 earlySingletonObjects 緩存從三級緩存中獲取到的bean,此時裡面的bean沒有載入完畢。
三級緩存 singletonFactories 。緩存一個objectFactory工廠。
場景:a依賴b,b依賴a和c,c依賴a。並且a,b,c都aop增強。
載入過程:
a實例化,放入三級工廠緩存,設置屬性b,b實例化放入三級緩存。b設置屬性a,從三級工廠緩存中獲取代理後的對象a,同時,代理後的a放入二級緩存,然後設置屬性c,c實例化放入三級緩存,設置屬性a,此時從二級緩存中獲取到的代理後的a跟b中的a是一個對象,屬性a設置成功。c初始化,然後執行後置處理器。進行aop的增強。增強後將代理的c放入到一級緩存,同時刪除三級緩存中的c。c載入完成,b得到c,b設置c成功。b初始化,然後執行後置處理器,進行aop增強,將增強後的代理對象b放入到一級緩存。刪除三級緩存中的b。此時 a拿到b,設置屬性b成功,開始初始化,初始化後執行後置處理器。在aop的後置處理器中有一個以beanName為key,經過aop增強的代理對象為value的map earlyProxyReferences。
這個時候 後置處理器處理對象a的時候,
public (@Nullable Object bean, String beanName) {
if (bean !=null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
也就是 發現這個beanName已經被代理後就不在代理。這個時候執行後置處理器後,a還是未經代理的對象a。此時a再通過getSingleton 重新從緩存中獲取一下a。
Object earlySingletonReference = getSingleton(beanName, false);
false 表示不從三級緩存中取,只從一級,二級緩存中獲取。
這個時候能拿到二級緩存中的a。二級緩存中的a也是經過代理後的a。
然後將代理後的a放入到一級緩存中。a載入完畢。
放入一級緩存的過程 :
addSingleton(beanName, singletonObject);
從三級工廠緩存中獲取對象:
protected ObjectgetEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && ()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bpinstanceof ) {
ibp = () bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
其中 AbstractAutoProxyCreator實現該介面。
public ObjectgetEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
wrapIfNecessary()就是真正執行代理的。
bean初始化之後執行的後置處理器:
其中AbstractAutoProxyCreator 實現了該介面。
B. Spring AOP 一般用在什麼場景中
AOP,在程序開發中主要用來解決一些系統層面上的問題,比如日誌,事務,許可權等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。
在不改變原有的邏輯的基礎上,增加一些額外的功能。代理也是這個功能,讀寫分離也能用aop來做。
(2)aop在緩存設計上的應用擴展閱讀:
AOP/OOP區分
AOP、OOP在字面上雖然非常類似,但卻是面向不同領域的兩種設計思想。OOP(面向對象編程)針對業務處理過程的實體及其屬性和行為進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
而AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。這兩種設計思想在目標上有著本質的差異。
C. 「Spring 」「AOP 容器」不看源碼就帶你認識核心流程以及運作原理
前一篇文章主要介紹了 spring 核心特性機制的 IOC 容器機制和核心運作原理,接下來我們去介紹另外一個較為核心的功能,那就是 AOP 容器機制,主要負責承接前一篇代理模式機制中動態代理:JDKProxy 和 CglibProxy 的功能機制之後,我們開始研究一下如何實現一下相關的 AOP 容器代理機制的。
實現的基本實現原理就是後置處理器:BeanPostProcessor 機制,實現動態化植入機制。
bean 在初始化的時候會進行調用對應的 BeanPostProcessor 的對應的方法會進行織入。
主要取決於 wrapIfNecessary 方法:
如果是基礎設施類型,則直接回進行返回該 bean 對象,不會進行相關的初始化對應的 aspectj 的動態織入機制。
會進行尋找相關的 Bean 對應的何時的加強通知類。
則會對該 bean 對象,額外進行增強操作生成相關的代理對象,並返回該執行之後的對象,否則會直接返回該對象即可。
getAdvicesAndAdvisorsForBean 方法是我們篩選 Advice 增強類的核心方法,主要用於過濾和篩選對應該 bean 的何時的增強器數組信息。
主要用於調用 的**findCandidateAdvisors()**方法,其內部會進行先關的核心構建相關的 Aspectj 的類的相關實現操作
advisorsFactory.getAdvisors 獲取通知器
切點類處理操作到此為止,還不完整接下來才是構建動態代理對象的真正執行操作,
擴展相關的篩選出的通知器列表,extendAdvisors 方法,通知器列表首部添加一個 DefaultPointcutAposr 類型的通知器,也就是 ExposeInvocationInterceptor.ADVISOR 的實現機制。
proxy-target-class 的屬性值,代表是否可以支持代理實現類,默認採用的 false 代表著,當 bean 有實現介面的時候,會直接採用 jdk 的動態代理機制生成代理對象,如果是 true,則代表著使用 cglib 進行生成代理對象。
復制代碼
前提是必須要配置相關的 expose-proxy 屬性配置值為 true,才會進行暴露對應的代理機制。
為了解決目標方法調用同對象中的其他方法,其他方法的切面邏輯是無法實現,因為會涉及到相關的 this 操作而不是 proxy 對象機制。
可以實現使用 AopContext.currentProxy()強制轉換為當前的代理對象。
獲取相關的對應方法的攔截器棧鏈路,如果沒有獲取到相關的緩存鏈路,則會直接調用相關的 獲取先關的攔截器鏈。
會進行先關的 PointcutAdvisor 類型通知器,這里會調用相關的通知器所持有的切點(Pointcut)對類和方法進行匹配,匹配沖過這說明相關的向當前的方法進行織入邏輯控制。此外還會通過 geIntercptors()方法對非 MethodIntercptor 類型的通知進行轉換。返回相關的攔截器數組,並且隨後存入緩存中。
則會直接通過代理機制的反射控制進行調用執行即可。
則例如 jdkDynamicAutoProxy 對象進行調用構建 ReflectiveMethodInvocation 對象,例如它的 process 方法啟動攔截器棧的 invoke 方法。
處理返回值,並且返回該值。