日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Spring為什么使用三級(jí)緩存而不是兩級(jí)解決循環(huán)依賴問(wèn)題?

?首先明確一點(diǎn),Spring如果使用二級(jí)緩存也是完全能夠解決代理bean的循環(huán)依賴問(wèn)題的。那Spring為什么要使用三級(jí)緩存的設(shè)計(jì)呢?在回答這個(gè)問(wèn)題前我們先明確一些概念。

Spring Bean相關(guān)的知識(shí)

Spring Bean 的創(chuàng)建過(guò)程

  1. 掃描xml或者注解獲取BeanDefinition;
  2. 實(shí)例化bean:通過(guò)createBeanInstance方法創(chuàng)建bean的原始對(duì)象BeanWrapper;
  3. 注入bean的依賴:利用populateBean方法(本質(zhì)是反射)注入bean的依賴屬性;
  4. 初始化bean:調(diào)用initializeBean方法最終形成完整的bean對(duì)象;

Spring Bean 的三級(jí)緩存定義

三級(jí)緩存的查找策略是,先從一級(jí)緩存獲取,若獲取不到就從二級(jí)緩存,仍然獲取不到則從三級(jí)緩存獲取,若還是獲取不到則通過(guò)bean對(duì)應(yīng)的BeanDefinition信息實(shí)例化。

Tips:二、三級(jí)緩存會(huì)在DI的過(guò)程中被刪除,最終所有的Bean都會(huì)變成完整的bean并存入一級(jí)緩存中。

  • 三級(jí)緩存singletonFactories:在注入bean的依賴前存入,所有bean都會(huì)存入,循環(huán)依賴時(shí)會(huì)使用,代碼如下:
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
  • 二級(jí)緩存earlySingletonObjects:存放實(shí)例化的對(duì)象(可能是原始對(duì)象也可能是代理對(duì)象),代碼如下:
/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
  • 一級(jí)緩存singletonObjects:用于存放完整的bean,代碼如下:
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);

什么是循環(huán)依賴?

循環(huán)依賴是指:Spring在初始化A的時(shí)候需要注入B,而初始化B的時(shí)候需要注入A,在Spring啟動(dòng)完成后這倆個(gè)對(duì)象都必須是完整的bean。

循環(huán)依賴的場(chǎng)景有三種:

  • 構(gòu)造器循環(huán)依賴:Spring無(wú)法解決,因?yàn)閎ean創(chuàng)建的第一步就是通過(guò)構(gòu)造器實(shí)例化,也就是說(shuō)解決循環(huán)依賴的前提就是對(duì)象可以實(shí)例化并緩存,與Java死鎖很像;
  • prototype范圍的依賴:該循環(huán)依賴Spring不可解決,prototype作用域的bean Spring不緩存,因此在依賴注入時(shí)無(wú)法獲取到依賴的bean;
  • setter循環(huán)依賴:該循環(huán)依賴是Spring推薦的方式,我們接下來(lái)就重點(diǎn)講解這種方式;

一個(gè)簡(jiǎn)單setter循環(huán)依賴的代碼示例如下:

@Service
public class A {
// @Autowired也行
@Resource
private B b;
}

@Service
public class B {
// @Autowired也行
@Resource
private A a;
}

Spring 是如何利用多級(jí)緩存解決循環(huán)依賴的

我們先拋開(kāi)Spring的實(shí)現(xiàn)來(lái)做一次解決循環(huán)依賴的設(shè)計(jì)推演。

在沒(méi)有緩存的情況下循環(huán)依賴的場(chǎng)景

如圖可以直接觀察到,當(dāng)沒(méi)有緩存時(shí),當(dāng)發(fā)生循環(huán)依賴時(shí)直接死循環(huán)了,最終的結(jié)局就是StackOverflow或者OOM。

增加一層緩存

為了解決上面循環(huán)依賴的問(wèn)題,我們加入一層緩存,緩存可以使用Map結(jié)構(gòu),key為beanName,value為對(duì)象實(shí)例。如下如:

從上圖可以直觀的看出,循環(huán)依賴的問(wèn)題已經(jīng)得到了完美解決,但是又有了一個(gè)新問(wèn)題,這個(gè)緩存中的bean可能有已經(jīng)創(chuàng)建完成的、正在創(chuàng)建中還沒(méi)有注入依賴的,它們都摻雜在一起,我們?nèi)绾伪WCMap里面的所有對(duì)象是完整的呢?一層緩存很顯然不符合設(shè)計(jì)規(guī)范,也缺乏安全性與擴(kuò)展性。

二級(jí)緩存設(shè)計(jì)

我們希望的是,明確已經(jīng)構(gòu)建完成的的Bean被放入到一個(gè)緩存中,創(chuàng)建中的bean在另外一個(gè)緩存中,于是就有了下面的結(jié)構(gòu):

與一級(jí)緩存架構(gòu)設(shè)計(jì)的區(qū)別在于:

  • 新增了二級(jí)緩存,用于存放剛實(shí)例化的bean;
  • 當(dāng)bean初始化完成后會(huì)放入一級(jí)緩存,同時(shí)將bean從二級(jí)緩存中的刪除(不需要一式兩份,保留一份最終完整的Bean即可)

從目前看這個(gè)設(shè)計(jì)完美解決了bean的完整性問(wèn)題,但是在實(shí)際生產(chǎn)中問(wèn)題總是疊著問(wèn)題,沒(méi)有完美的架構(gòu)設(shè)計(jì)。我們都知道Java中有代理,而且代理的應(yīng)用非常廣泛,包括在Spring中就有非常多的代理,那問(wèn)題就來(lái)了,我們?nèi)绾螀^(qū)分代理對(duì)象與普通對(duì)象?如果循環(huán)依賴中存在代理對(duì)象的循環(huán)依賴會(huì)發(fā)生什么呢?

代理對(duì)象的循環(huán)依賴

在現(xiàn)實(shí)開(kāi)發(fā)過(guò)程中,我們往往會(huì)產(chǎn)生很多的代理對(duì)象,當(dāng)存在代理對(duì)象加入到循環(huán)依賴流程會(huì)是什么樣的場(chǎng)景,我們來(lái)推演一下,我們?nèi)匀皇褂枚?jí)緩存的設(shè)計(jì)來(lái)做推演。

如果我們?cè)赽ean初始化完成之后再創(chuàng)建代理對(duì)象,整個(gè)流程是這樣的:

從上圖可以非常直觀的看出,最終在一級(jí)緩存中的對(duì)象A是一個(gè)proxy_A,但是對(duì)象B依賴的對(duì)象A卻是一個(gè)普通A!很明顯現(xiàn)有的設(shè)計(jì)不能夠滿足代理對(duì)象的循環(huán)依賴問(wèn)題。

如何解決這個(gè)問(wèn)題呢?我們還是在上一個(gè)設(shè)計(jì)上做修改:

  • 方案一:在獲取到A時(shí)立即創(chuàng)建代理,如下圖(紅色部分)所示:

這個(gè)方案看起來(lái)解決了B對(duì)象依賴不到A的proxy對(duì)象問(wèn)題,但是又引起了一個(gè)致命的問(wèn)題,在A初始化完成之后還會(huì)創(chuàng)建一次代理對(duì)象,那么就創(chuàng)建了兩次代理對(duì)象,他們是完全不一樣的,這個(gè)代理對(duì)象不是單例的了!

  • 方案二:在方案一的基礎(chǔ)上,我們是不是可以將創(chuàng)建完的proxy_A對(duì)象加入到二級(jí)緩存中,直接覆蓋掉普通A(代理對(duì)象會(huì)持有普通對(duì)象A的引用,所以可以覆蓋):這個(gè)方案看上去沒(méi)有問(wèn)題,但是從設(shè)計(jì)角度講,這不符合設(shè)計(jì)規(guī)范,而且覆蓋后的A是個(gè)代理對(duì)象,在后續(xù)的操作中,如果再?gòu)亩?jí)緩存中獲取A,這時(shí)候就不知道到底獲取到的是普通A還是proxy_A了,這無(wú)形增加了判斷識(shí)別的復(fù)雜度。
  • 方案三:在首次實(shí)例化A的時(shí)候就直接創(chuàng)建A的代理對(duì)象,并放入二級(jí)緩存中:這個(gè)方案與方案二有相同的問(wèn)題,這里不在贅述。

解決代理對(duì)象的循環(huán)依賴之終極方案

解決代理對(duì)象的循環(huán)依賴之終極方案-三級(jí)緩存!

與二級(jí)緩存設(shè)計(jì)最大的不同點(diǎn)在于:

  • 在獲取到A時(shí)創(chuàng)建proxy_A,同時(shí)將其加入到二級(jí)緩存中,并返回給B,這樣B就依賴了proxy_A;
  • 在A初始化過(guò)程中會(huì)創(chuàng)建代理對(duì)象,這時(shí)候會(huì)做一個(gè)檢查,也就是會(huì)去查詢二級(jí)緩存,看有沒(méi)有proxy_A的存在,如果有說(shuō)明proxy_A已經(jīng)創(chuàng)建,我們會(huì)選擇二級(jí)緩存中的proxy_A存入一級(jí)緩存并返回(因?yàn)槎?jí)緩存中的proxy_A已經(jīng)被B依賴);

其它流程不在贅述,該設(shè)計(jì)中最重要的幾個(gè)地方在Spring中的實(shí)現(xiàn)是更加細(xì)致的,我在流程途中只是簡(jiǎn)單概括,下面特殊說(shuō)明一下幾個(gè)點(diǎn)。

Spring Bean初始化會(huì)產(chǎn)生代理對(duì)象的場(chǎng)景

在上述流程中,標(biāo)記位黃色的部分就是兩個(gè)代理對(duì)象的創(chuàng)建的地方,在Spring中就是這兩個(gè)后置處理器調(diào)用的地方,它們分別是:

  • 在調(diào)用getEarlyBeanReference時(shí)如果實(shí)現(xiàn)了BeanPostProcessor則會(huì)創(chuàng)建代理對(duì)象;
  • 另一個(gè)地方是在執(zhí)行Bean初始化initializeBean時(shí)執(zhí)行BeanPostProcessor會(huì)創(chuàng)建代理對(duì)象;

在Spring中的第三級(jí)緩存有更加靈活設(shè)計(jì)

在Spring中,第三級(jí)緩存不僅僅是存入了實(shí)例化的對(duì)象,而是存入了一個(gè)匿名類ObjectFactory,getEarlyBeanReference函數(shù)的實(shí)現(xiàn)中會(huì)調(diào)用BeanPostProcessor執(zhí)行用戶自定義的邏輯。具體代碼如下:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// addSingletonFactory方法是將bean加入三級(jí)緩存中
// 三級(jí)緩存會(huì)被ObjectFactory包裝
// getEarlyBeanReference方法會(huì)執(zhí)行Bean的后置處理器
addSingletonFactory(beanName, new ObjectFactory() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}

若初始化階段的后置處理器對(duì)對(duì)象做了代理,Spring是如何處理的?

在Spring中若在initializeBean階段的后置處理器對(duì)對(duì)象做了代理,那么Spring會(huì)對(duì)做依賴檢查,具體代碼如下:

if (earlySingletonExposure) {
// 這里我們還拿A、B兩個(gè)對(duì)象舉例
// 嘗試從二級(jí)緩存中獲取A,第二個(gè)參數(shù)是false表示不再?gòu)娜?jí)緩存獲取(也就是執(zhí)行ObjectFactory.getObject())
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// exposedObject是執(zhí)行initializeBean方法返回的A,可能是個(gè)proxy_A
// bean是首次實(shí)例化的A,若這兩個(gè)對(duì)象不相等,說(shuō)明initializeBean方法返回了代理對(duì)象,需要進(jìn)行依賴檢查
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 依賴檢查邏輯
// 這一大段就是在檢查,檢查依賴了A對(duì)象的Bean集合
// 這里很好理解:例如B依賴了A,那么如果B沒(méi)有創(chuàng)建好,那么我們把B從緩存刪掉,在之后的構(gòu)建中讓其重新依賴A_proxy
// 若B已經(jīng)創(chuàng)建好了,那么很不幸,只能報(bào)錯(cuò)了,因?yàn)锽這時(shí)候依賴的是一個(gè)普通A,而不是proxy_A
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] 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.");
}
}
}
}

為什么Spring采用三級(jí)緩存設(shè)計(jì)?

我們?cè)倩氐阶畛醯膯?wèn)題上,其實(shí)上述整個(gè)設(shè)計(jì)推演過(guò)程就已經(jīng)很好的回答了這個(gè)問(wèn)題,這里再做一下補(bǔ)充。從上述Spring源碼可知,其在第三級(jí)緩存中放入的是匿名類ObjectFactory,每次需要獲取對(duì)象實(shí)例就會(huì)調(diào)用其getObject方法。我們舉個(gè)例子:

假如現(xiàn)在沒(méi)有earlySingletonObjects這一層緩存(也就是第二級(jí)緩存),也就是兩級(jí)緩存結(jié)構(gòu),現(xiàn)在有三個(gè)對(duì)象,其依賴關(guān)系如下A->B、B->A和C、C->A,從這個(gè)依賴關(guān)系可以得出,A所在的ObjectFactory會(huì)被調(diào)用兩次getObject(),如果兩次都返回不同的proxy_A(畢竟后置處理器的代碼是使用者自己寫(xiě)的,可能代碼是new Proxy(A)),那么就可能導(dǎo)致,B、C對(duì)象依賴的proxy_A不是一個(gè)對(duì)象,那么這種設(shè)計(jì)是致命的。

這個(gè)案例也從側(cè)面反映了三層緩存的設(shè)計(jì)必要性、必然性,也是為了讓框架更加的靈活健壯,以上就是我對(duì)Spring bean 三層緩存設(shè)計(jì)的理解,如有疑問(wèn)歡迎在評(píng)論區(qū)討論留言。


網(wǎng)頁(yè)題目:Spring為什么使用三級(jí)緩存而不是兩級(jí)解決循環(huán)依賴問(wèn)題?
文章URL:http://www.dlmjj.cn/article/dpgpjgh.html