天天看點

Spring5源碼分析------循環依賴(一)

1.循環依賴

發生在bean的注冊過程中,bean A依賴于另一個bean B時,bean B依賴于bean A

代碼解釋:

Spring5源碼分析------循環依賴(一)
Spring5源碼分析------循環依賴(一)

這樣就是循環依賴。

不了解spring bean的建立請點選檢視

那麼是怎麼産生循環的呢?我們在學習了bean的建立的時候知道,bean對象的執行個體化完成之後才是進行初始化,在初始化的過程中進行了屬性的注入(依賴注入)。在AService中的屬性中有着BService對象,是以在注入屬性的時候,BService就作為了早期暴露bean(早期暴露指bean不在其他bean中,正常情況是一個一個bean進行注冊)。

循環依賴引用異常

在平常我們使用循環依賴時,一般不會出現循環依賴異常。因為底層已經幫我們進行了單例循環依賴的處理,那麼我們要怎麼去重制這個異常呢?

應該都注意到了底層是實作的單例,那麼我們可以用多例(原型模式)去重制。

AService.java:

package com.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AService {

    @Autowired
    BService bService;

}

           

BService.java:

package com.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BService {

    @Autowired
    AService aService;
}

           

Myconfig.java:

package com.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.service")
public class MyConfig {
}

           

Application.java:

package com;

import com.config.MyConfig;
import com.service.AService;
import com.service.BService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        AService aService = annotationConfigApplicationContext.getBean("AService", AService.class);
        BService bService = annotationConfigApplicationContext.getBean("BService", BService.class);
    }
}
           
Spring5源碼分析------循環依賴(一)

點開

AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:274)

會發現是這一段代碼抛出的異常

Spring5源碼分析------循環依賴(一)

循環依賴異常就重制了。那麼我們在項目中沒辦法隻能用多例,怎麼去解決循環依賴問題呢?

其實很簡單。這樣更改代碼即可:

AService.java:

package com.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype")
public class AService {

//    @Autowired
    BService bService;

    public void setbService(BService bService) {
        this.bService = bService;
    }
}
           

BService.java:

package com.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype")
public class BService {

//    @Autowired
    AService aService;

    public void setaService(AService aService) {
        this.aService = aService;
    }
}
           

Application.java:

package com;

import com.config.MyConfig;
import com.service.AService;
import com.service.BService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        AService aService = annotationConfigApplicationContext.getBean("AService", AService.class);
        BService bService = annotationConfigApplicationContext.getBean("BService", BService.class);
        aService.setbService(bService);
        bService.setaService(aService);
    }
}
           

我們手動去set對象,就可以解決多例的循環依賴問題。因為在底層中,每使用一次ASerivce/BService他都是不同的對象,是以找不到循環依賴的出口,導緻會死循環(當然,底層并不會死循環很久才報錯,而是通過特判抛出異常的)

基礎知識了解

執行個體化:代表對象隻是建立成功,并未對其屬性進行指派

初始化:代表對象建立成功,并已成功對其屬性進行指派

完整對象:初始化完成的對象

嬰兒對象:執行個體化完成的對象

循環依賴源碼檢視

了解過前面bean的建立的朋友,應該知道了,bean的初始化是建立對象之後進行屬性指派,而循環依賴的出現正是屬性指派過程中,是以我們這裡隻進行核心部分展示(循環依賴部分)

我們先從

protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)

開始看起。

Spring5源碼分析------循環依賴(一)

在String beanName = transformedBeanName(name);中擷取beanName。

檢視getSingleton(beanName);方法,此方法是在緩存中查詢此bean是否注冊或者在注冊的過程中。下面我們檢視一下此方法内部邏輯

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 一級緩存(singletonObjects)查詢bean是否存在
        Object singletonObject = this.singletonObjects.get(beanName);
        // isSingletonCurrentlyInCreation判斷是否處于正在建立的途中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            // 二級緩存(earlySingletonObjects)查詢bean是否存在
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // 一級緩存(singletonObjects)查詢bean是否存在
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 從二級緩存中取出bean對象
                        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;
    }
           

然後在回到AbstractBeanFactory中doGetBean的if (isPrototypeCurrentlyInCreation(beanName)) 我們可以看到之前的循環引用異常的地方。

之後在看到

if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
           

這裡有個Lambda 表達式,裡面涉及到了createBean,我們先進去看看這個表達式做了什麼

// 一級緩存中查詢
Object singletonObject = this.singletonObjects.get(beanName);
           
// 将此對象添加正在建立過程的bean集合singletonsCurrentlyInCreation
beforeSingletonCreation(beanName);
           
try {
	// 調用Lambda 表達式中的createBean(beanName, mbd, args)
	singletonObject = singletonFactory.getObject();
	newSingleton = true;
}
           

進入之後調用doCreateBean

進入之後有這麼一段

if (instanceWrapper == null) {
	// 通過反射建立bean執行個體
	instanceWrapper = createBeanInstance(beanName, mbd, args);
}
           

再往下走

// 添加嬰兒對象到三級緩存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
           
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
			// 添加到三級緩存
			this.singletonFactories.put(beanName, singletonFactory);
			//移除二級緩存中的bean(二級和三級不共存)this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}
           

然後就到達了我們最熟悉的代碼

try {
	// 屬性注入
	populateBean(beanName, mbd, instanceWrapper);
	// 代理
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}
           

再進入populateBean到

// 屬性注入
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
	PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
           

因為要建立對象是以最後傳回到了protected T doGetBean,用于建立BService。再一次循環走上面的步驟直到再一次打算建立AService。直到到了protected Object getSingleton(String beanName, boolean allowEarlyReference) 方法發生了變化。

Spring5源碼分析------循環依賴(一)

直接進入到了最底層代碼,将AService從三級緩存釋放,放入到二級緩存,因為三級緩存中存在AService的嬰兒對象,也就是執行個體化的對象AService,是以在BService屬性注入的時候,将嬰兒對象AService指派給了BService的屬性。那麼BService的初始化就直接完成了。

那麼BService直接進入這個方法,進行二次處理

if (earlySingletonExposure) {
	// 對bean進行第二次處理
	Object earlySingletonReference = getSingleton(beanName, false);
           

之後沒什麼特别重要的。。。

最後來到DefaultSingletonBeanRegistry中public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)的

if (newSingleton) {
	// 将BService添加到一級緩存,删除二級緩存BService
	addSingleton(beanName, singletonObject);
}
           

之後跳出所有關于BService的方法。繼續進行AService方法的調用,最後在

Spring5源碼分析------循環依賴(一)

這個set中注入BService。至此,循環依賴已經結束

Spring5源碼分析------循環依賴(一)

接下來進行和之前一樣的操作,将AService放在一級緩存,移除三級緩存的資料。可能會有人疑問了,貌似并不需要三級緩存,隻用一級和二級緩存即可完成循環依賴操作。這個問題放在循環依賴(二)來講,細心的會發現我在前面提到了二次處理,這個疑問也下次再解答