新聞中心
以下內(nèi)容基于 Spring6.0.4。

創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿(mǎn)足客戶(hù)于互聯(lián)網(wǎng)時(shí)代的登封網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
關(guān)于 Spring 循環(huán)依賴(lài),松哥已經(jīng)連著發(fā)了三篇文章了,本篇文章松哥從源碼的角度來(lái)和小伙伴們捋一捋 Spring 循環(huán)依賴(lài)到底是如何解決了。
小伙伴們一定要先熟悉前面文章的內(nèi)容,否則今天的源碼可能會(huì)看起來(lái)有些吃力。
接下來(lái)我通過(guò)一個(gè)簡(jiǎn)單的循環(huán)依賴(lài)的案例,來(lái)和大家梳理一下完整的 Bean 循環(huán)依賴(lài)處理流程。
1. 案例設(shè)計(jì)
假設(shè)我有如下 Bean:
@Service
public class A {
@Autowired
B b;
}
@Service
public class B {
@Autowired
A a;
}就這樣一個(gè)簡(jiǎn)單的循環(huán)依賴(lài),默認(rèn)情況下,A 會(huì)被先加載,然后在 A 中做屬性填充的時(shí)候,去創(chuàng)建了 B,創(chuàng)建 B 的時(shí)候又需要 A,就會(huì)從緩存中拿到 A,大致流程如此,接下來(lái)我們結(jié)合源碼來(lái)驗(yàn)證一下這個(gè)流程。
2. 源碼分析
首先我們來(lái)看獲取 Bean 的時(shí)候,如何利用這三級(jí)緩存。
小伙伴們知道,獲取 Bean 涉及到的就是 getBean 方法,像我們上面這個(gè)案例,由于都是單例的形式,所以 Bean 的初始化其實(shí)在容器創(chuàng)建的時(shí)候就完成了。
圖片
在 preInstantiateSingletons 方法中,又調(diào)用到 AbstractBeanFactory#getBean 方法,進(jìn)而調(diào)用到 AbstractBeanFactory#doGetBean 方法。
圖片
Bean 的初始化就是從這里開(kāi)始的,我們就從這里來(lái)開(kāi)始看起吧。
2.1 doGetBean
AbstractBeanFactory#doGetBean(方法較長(zhǎng),節(jié)選部分關(guān)鍵內(nèi)容):
protected T doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory abf) {
return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
.tag("beanName", name);
try {
if (requiredType != null) {
beanCreation.tag("beanType", requiredType::toString);
}
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
} 這個(gè)方法比較長(zhǎng),我來(lái)和大家說(shuō)幾個(gè)關(guān)鍵的點(diǎn):
- 首先這個(gè)方法一開(kāi)始就調(diào)用了 getSingleton 方法,這個(gè)是嘗試從三級(jí)緩存中獲取到想要的 Bean,但是,當(dāng)我們第一次初始化 A 的時(shí)候,很顯然這一步是無(wú)法獲取到 A 的實(shí)例的,所以這一步會(huì)返回 null。
- 如果第一步拿到了 Bean,那么接下來(lái)就進(jìn)入到 if 分支中,直接獲取到想要的 beanInstance 實(shí)例;否則進(jìn)入到第三步。
- 如果第一步?jīng)]有從三級(jí)緩存中拿到 Bean,那么接下來(lái)就要檢查是否是循環(huán)依賴(lài)了,首先調(diào)用 isPrototypeCurrentlyInCreation 方法判斷當(dāng)前 Bean 是否已經(jīng)在創(chuàng)建了,如果已經(jīng)在創(chuàng)建了,那么顯然要拋異常出去了(BeanCurrentlyInCreationException)。接下來(lái)就去 parent 容器中各種查找,看能否找到需要的 Bean,Spring 中的父子容器問(wèn)題松哥在之前的文章中也已經(jīng)講過(guò)了,小伙伴們可以參考:Spring 中的父子容器是咋回事?。
- 如果從父容器中也沒(méi)找到 Bean,那么接下來(lái)就會(huì)調(diào)用 markBeanAsCreated 方法來(lái)標(biāo)記當(dāng)前 Bean 已經(jīng)創(chuàng)建或者正準(zhǔn)備創(chuàng)建。
- 接下來(lái)會(huì)去標(biāo)記一下創(chuàng)建步驟,同時(shí)檢查一下 Bean 的 dependsOn 屬性是否存在循環(huán)關(guān)系,這些跟我們本文關(guān)系都不大,我就不去展開(kāi)了。
- 關(guān)鍵點(diǎn)來(lái)了,接下來(lái)判斷如果我們當(dāng)前 Bean 是單例的,那么就調(diào)用 getSingleton 方法去獲取一個(gè)實(shí)例,該方法的第二個(gè)參數(shù)一個(gè) Lambda 表達(dá)式,表達(dá)式的核心內(nèi)容就是調(diào)用 createBean 方法去創(chuàng)建一個(gè) Bean 實(shí)例,該方法將不負(fù)眾望,拿到最終想要的 Bean。
以上就是 doGetBean 方法中幾個(gè)比較重要的點(diǎn)。
其中有兩個(gè)方法我們需要展開(kāi)講一下,第一個(gè)方法就是去三級(jí)緩存中查詢(xún) Bean 的 getSingleton 方法(步驟一),第二個(gè)方法則是去獲取到 Bean 實(shí)例的 getSingleton 方法(步驟六),這是兩個(gè)重載方法。
接下來(lái)我們就來(lái)分析一下這兩個(gè)方法。
2.2 查詢(xún)?nèi)?jí)緩存
DefaultSingletonBeanRegistry#getSingleton:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
- 首先去 singletonObjects 中查找,這就是所謂的一級(jí)緩存,如果這里能直接找到想要的對(duì)象,那么直接返回即可。
- 如果一級(jí)緩存中不存在想要的 Bean,那么接下來(lái)就該去二級(jí)緩存 earlySingletonObjects 中查找了,二級(jí)緩存要是有我們想要的 Bean,那么也是直接返回即可。
- 二級(jí)緩存中如果也不存在,那么就是加鎖然后去三級(jí)緩存中查找了,三級(jí)緩存是 singletonFactories,我們從 singletonFactories 中獲取到的是一個(gè) ObjectFactory 對(duì)象,這是一個(gè) Lambda 表達(dá)式,調(diào)用這里的 getObject 方法最終有可能會(huì)促成提前 AOP,至于這個(gè) Lambda 表達(dá)式的內(nèi)容,松哥在前面的文章中已經(jīng)和小伙伴們介紹過(guò)了,這里先不啰嗦(如何通過(guò)三級(jí)緩存解決 Spring 循環(huán)依賴(lài))。
- 如果走到三級(jí)緩存這一步了,從三級(jí)緩存中拿到了想要的數(shù)據(jù),那么就把數(shù)據(jù)存入到二級(jí)緩存 earlySingletonObjects 中,以備下次使用。同時(shí),移除三級(jí)緩存中對(duì)應(yīng)的數(shù)據(jù)。
當(dāng)我們第一次創(chuàng)建 A 對(duì)象的時(shí)候,很顯然三級(jí)緩存中都不可能有數(shù)據(jù),所以這個(gè)方法最終返回 null。
2.3 獲取 Bean 實(shí)例
接下來(lái)看 2.1 小節(jié)步驟六的獲取 Bean 的方法。
DefaultSingletonBeanRegistry#getSingleton(方法較長(zhǎng),節(jié)選部分關(guān)鍵內(nèi)容):
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
- 這個(gè)方法首先也是嘗試從一級(jí)緩存中獲取到想要的 Bean,如果 Bean 為 null,就開(kāi)始施法了。
- 首先會(huì)去判斷一下,如果這個(gè)工廠(chǎng)的單例正在銷(xiāo)毀,那么這個(gè) Bean 的創(chuàng)建就不被允許。
- 接下來(lái)會(huì)有一堆準(zhǔn)備工作,關(guān)鍵點(diǎn)在 singletonFactory.getObject(); 地方,這個(gè)就是方法第二個(gè)參數(shù)傳進(jìn)來(lái)的回調(diào)函數(shù),將來(lái)在回調(diào)函數(shù)中,會(huì)調(diào)用到 createBean 方法,真正開(kāi)始 A 這個(gè) Bean 的創(chuàng)建。將 A 對(duì)象創(chuàng)建成功之后,會(huì)把 newSingleton 設(shè)置為 true,第 4 步會(huì)用到。
- 現(xiàn)在調(diào)用 addSingleton 方法,把創(chuàng)建成功的 Bean 添加到緩存中。
我們來(lái)看下 addSingleton 方法:
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}小伙伴們看一下,一級(jí)緩存中存入 Bean,二級(jí)緩存和三級(jí)緩存移除該 Bean,同時(shí)在 registeredSingletons 集合中記錄一下當(dāng)前 Bean 已經(jīng)創(chuàng)建。
所以現(xiàn)在的重點(diǎn)其實(shí)又回到了 createBean 方法了。
2.4 createBean
createBean 方法其實(shí)就到了 Bean 的創(chuàng)建流程了。bean 的創(chuàng)建流程在前面幾篇 Spring 源碼相關(guān)的文章中也都有所涉獵,所以今天我就光說(shuō)一些跟本文主題相關(guān)的幾個(gè)點(diǎn)。
createBean 方法最終會(huì)調(diào)用到 AbstractAutowireCapableBeanFactory#doCreateBean 方法,這個(gè)方法也是比較長(zhǎng)的,而我是關(guān)心如下幾個(gè)地方:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 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) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
return exposedObject;
}這里我比較在意的有兩個(gè)地方,一個(gè)是調(diào)用 addSingletonFactory 方法向三級(jí)緩存中添加回調(diào)函數(shù),回調(diào)函數(shù)是 getEarlyBeanReference,如果有需要,將來(lái)會(huì)通過(guò)這個(gè)回調(diào)提前進(jìn)行 AOP,即使沒(méi)有 AOP,就是普通的循環(huán)依賴(lài),三級(jí)緩存也是會(huì)被調(diào)用的,這個(gè)大家繼續(xù)往后看就知道了,另外還有一個(gè)比較重要的地方,在本方法一開(kāi)始的時(shí)候,就已經(jīng)創(chuàng)建出來(lái) A 對(duì)象了,這個(gè)時(shí)候的 A 對(duì)象是一個(gè)原始 Bean,即單純的只是通過(guò)反射把對(duì)象創(chuàng)建出來(lái)了,Bean 還沒(méi)有經(jīng)歷過(guò)完整的生命周期,這里 getEarlyBeanReference 方法的第三個(gè)參數(shù)就是該 Bean,這個(gè)也非常重要,牢記,后面會(huì)用到。
第二個(gè)地方就是 populateBean 方法,當(dāng)執(zhí)行到這個(gè)方法的時(shí)候,A 對(duì)象已經(jīng)創(chuàng)建出來(lái)了,這個(gè)方法是給 A 對(duì)象填充屬性用的,因?yàn)榻酉聛?lái)要注入 B 對(duì)象,就在這個(gè)方法中完成的。
由于我們第 1 小節(jié)是通過(guò) @Autowired 來(lái)注入 Bean 的,所以現(xiàn)在在 populateBean 方法也主要是處理 @Autowired 注入的情況,那么這個(gè)松哥之前寫(xiě)過(guò)文章,小伙伴們參考@Autowired 到底是怎么把變量注入進(jìn)來(lái)的?,具體的注入細(xì)節(jié)我這里就不重復(fù)了,單說(shuō)在注入的過(guò)程中,會(huì)經(jīng)過(guò)一個(gè) DefaultListableBeanFactory#doResolveDependency 方法,這個(gè)方法就是用來(lái)解析 B 對(duì)象的(至于如何到達(dá) doResolveDependency 方法的,小伙伴們參考 @Autowired 到底是怎么把變量注入進(jìn)來(lái)的?一文)。
doResolveDependency 方法也是比較長(zhǎng),我這里貼出來(lái)和本文相關(guān)的幾個(gè)關(guān)鍵地方:
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
//...
Map matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// We have exactly one match.
Map.Entry entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
//...
}
- 在這個(gè)方法中,首先調(diào)用 findAutowireCandidates 方法,以類(lèi)型為依據(jù),找到所有滿(mǎn)足條件的 Class 并組成一個(gè) Map 返回。例如第一小節(jié)的案例,這里就會(huì)找到所有 B 類(lèi)型的 Class,通過(guò)一個(gè) Map 返回。
- 如果第一步返回的 Map 存在多條記錄,那么就必須從中挑選一個(gè)出來(lái),這就是 matchingBeans.size() > 1 的情況。
- 如果第一步返回的 Map 只有一條記錄,那么就從 Map 中提取出來(lái) key 和 value,此時(shí)的 value 是一個(gè) Class,所以接下來(lái)還要調(diào)用 descriptor.resolveCandidate 去完成 Class 到對(duì)象的轉(zhuǎn)變。
而 descriptor.resolveCandidate 方法又開(kāi)啟了新一輪的 Bean 初始化,只不過(guò)這次初始化的 B 對(duì)象,如下:
public Object resolveCandidate(String beanName, Class> requiredType, BeanFactory beanFactory)
throws BeansException {
return beanFactory.getBean(beanName);
}
2.5 后續(xù)流程
后續(xù)流程其實(shí)就是上面的步驟,我就直接來(lái)跟大家說(shuō)一說(shuō),就不貼代碼了。
現(xiàn)在系統(tǒng)調(diào)用 beanFactory.getBean 方法去查找 B 對(duì)象,結(jié)果又是走一遍本文第二小節(jié)的所有流程,當(dāng) B 創(chuàng)建出來(lái)之后,也要去做屬性填充,此時(shí)需要在 B 中注入 A,那么又來(lái)到本文的 2.4 小節(jié),最終又是調(diào)用到 resolveCandidate 方法去獲取 A 對(duì)象。
此時(shí),在獲取 A 對(duì)象的過(guò)程中,又會(huì)調(diào)用到 doGetBean 這個(gè)方法,在這個(gè)方法中調(diào)用 getSingleton 的時(shí)候(2.1 小節(jié)的第一步),這個(gè)時(shí)候的執(zhí)行邏輯就跟前面不一樣了,我們?cè)賮?lái)看下這個(gè)方法的源碼:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}現(xiàn)在還是嘗試從三級(jí)緩存中獲取 A,此時(shí)一二級(jí)緩存中還是沒(méi)有 A,但是三級(jí)緩存中有一個(gè)回調(diào)函數(shù),當(dāng)執(zhí)行 singletonFactory.getObject() 方法的時(shí)候,就會(huì)觸發(fā)該回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)就是我們前面 2.4 小節(jié)提到的 getEarlyBeanReference 方法,我們現(xiàn)在來(lái)看下這個(gè)方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}這個(gè)方法有一個(gè)參數(shù) Bean,這個(gè)參數(shù) Bean 會(huì)經(jīng)過(guò)一些后置處理器處理之后返回,后置處理器主要是看一下這個(gè) Bean 是否需要 AOP,如果需要就進(jìn)行 AOP 處理,如果不需要,直接就把這個(gè)參數(shù) Bean 返回就行了。至于這個(gè)參數(shù)是哪來(lái)的,我在 2.4 小節(jié)中已經(jīng)加黑標(biāo)記出來(lái)了,這個(gè)參數(shù) Bean 其實(shí)就是原始的 A 對(duì)象!
好了,現(xiàn)在 B 對(duì)象就從緩存池中拿到了原始的 A 對(duì)象,B 對(duì)象屬性注入完畢,對(duì)象創(chuàng)建成功,進(jìn)而導(dǎo)致 A 對(duì)象也創(chuàng)建成功。
大功告成。
3. 小結(jié)
老實(shí)說(shuō),如果小伙伴們認(rèn)認(rèn)真真看過(guò)松哥最近發(fā)的 Spring 源碼文章,今天的內(nèi)容很好懂~至此,Spring 循環(huán)依賴(lài),從思路到源碼,都和大家分析完畢了~感興趣的小伙伴可以 DEBUG 走一遍哦~
本文標(biāo)題:透過(guò)源碼,捋清楚循環(huán)依賴(lài)到底是如何解決的!
網(wǎng)頁(yè)路徑:http://fisionsoft.com.cn/article/djocejj.html


咨詢(xún)
建站咨詢(xún)
