Springboot循環依賴實踐紀實


測試的Springboot版本: 2.6.4,禁止了循環依賴,但是可以通過application.yml開啟(哈哈)

@Lazy注解解決循環依賴

情況一:只有簡單屬性關系的循環依賴

涉及的Bean:

  • ASerivce及其實現類ASerivceImpl
  • BSerivce及其實現類BSerivceImpl

com.example.demo.service.AService

package com.example.demo.service;


public interface AService {
    void zaWaLuDo();
}

com.example.demo.service.impl.AServiceImpl

package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AServiceImpl implements AService {

    @Autowired
    public BService bService;

    @Override
    public void zaWaLuDo(){
        System.out.println("ToKiOToMaLei!");
    }
}

com.example.demo.service.BService

package com.example.demo.service;

public interface BService {

}

com.example.demo.service.impl.BServiceImpl

package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BServiceImpl implements BService {
    @Autowired
    public AService aService;
}

此時ASerivceBService構成循環依賴的關系:
A-B.png

測試類:com.example.demo.service.AServiceTest

package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class AServiceTest {
    @Autowired
    AService aService;

    @Test
    public void test(){
        aService.zaWaLuDo();
    }

}

此時運行test方法,將會報錯:

java.lang.IllegalStateException: Failed to load ApplicationContext

	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
	at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363)
......省略.....
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AServiceImpl': Unsatisfied dependency expressed through field 'bService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BServiceImpl': Unsatisfied dependency expressed through field 'aService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:659)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)

最重要的一句應該是:

美觀處理過:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: 
	Error creating bean with name 'AServiceImpl': Unsatisfied dependency expressed through field 'bService'; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: 
	Error creating bean with name 'BServiceImpl': Unsatisfied dependency expressed through field 'aService'; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: 
	Error creating bean with name 'AServiceImpl': Requested bean is currently in creation: 
		Is there an unresolvable circular reference?

Spring提醒我們可能存在circular reference,就是大名鼎鼎的循環依賴。

解決辦法

在其中任意一個屬性注入@Autowired上加入懶加載@Lazy即可跑通,比如在AService的實現類中加入:

package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class AServiceImpl implements AService {

    @Autowired
    @Lazy //懶加載
    public BService bService;

    @Override
    public void zaWaLuDo(){
        System.out.println("ToKiOToMaLei!");
    }
}

此時,運行測試方法test()的運行結果就是:

ToKiOToMaLei!

說明aService.zaWaLuDo()方法執行成功

源碼分析

參考:https://www.zhihu.com/question/438247718
image.png

主要是靠Spring中(人為定義)的三級緩存有關:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

第一級緩存:**Map<String, Object> singletonObjects**
第一級緩存的作用?

  • 用於存儲單例模式下創建的Bean實例(已經創建完畢)。
  • 該緩存是對外使用的,指的就是使用Spring框架的程序員。

存儲什么數據?

  • K:bean的名稱
  • V:bean的實例對象, 或者說:“成品”對象(有代理對象則指的是代理對象,已經創建完畢)

第二級緩存:**Map<String, Object> earlySingletonObjects**
第二級緩存的作用?

  • 用於存儲單例模式下創建的Bean實例(該Bean被提前暴露的引用,該Bean還在創建中)。
  • 該緩存是對內使用的,指的就是Spring框架內部邏輯使用該緩存。

存儲的數據:

  • K:bean的名稱
  • V:bean的實例對象,“半成品”對象(有代理對象則指的是代理對象,該Bean還在創建中)

第三級緩存:**Map<String, ObjectFactory<?>> singletonFactories**
第三級緩存的作用?

  • 通過ObjectFactory對象來存儲單例模式下提前暴露的Bean實例的引用(正在創建中)。
  • 該緩存是對內使用的,指的就是Spring框架內部邏輯使用該緩存。
  • 此緩存是解決循環依賴最大的功臣

存儲什么數據?

  • K:bean的名稱
  • V:ObjectFactory,該對象持有提前暴露的bean的引用

一、注入AService時,首先進入org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean:

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

看看getSingleton方法的原型,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

所以此時doGetBean方法會進入lambda方法中的,調用createBean方法來得到一個ObjectFactory
接着我們進入到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorydoCreateBean方法, 打上斷點看看:

  1. beanName='AServiceImpl'的時候,先根據反射創建了一個Object類的AServiceImpl的bean,里面的BServicenull
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
 	...省略...
        Object bean = instanceWrapper.getWrappedInstance(); //ASericeImpl@4686
    	Class<?> beanType = instanceWrapper.getWrappedClass(); //beanType = "class com.example.demo.service.impl.AServiceImpl"
    ...省略...
}
  1. 判斷該bean是否已經被提前暴露
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
    	...省略...
        
        //判斷該bean是否已經被提前暴露
       	//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));
    	//如果是,就調用addSingletonFactory方法,
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
   	 ...省略...
}
    
  1. 若沒有被提前暴露,就進入到語句:
		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
            //調用populateBean方法后,AService中的BService屬性就不再是null,而是一個$Proxy@4981$,
            //應該是個代理的對象,解決注入的燃眉之急
			populateBean(beanName, mbd, instanceWrapper);
            //做一些初始化的操作
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
  1. 將該bean暴露
		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
  1. 接着就將其返回
return exposedObject;

此時,exposedObject對象里的bService還是$Proxy$

image.png

二、回到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法:

				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
                            //這時候回到了這里,lambda表達式會得到上面的exposedObject
							return createBean(beanName, mbd, args);
						}

此時會回到getSingleton方法中,進入getSingleton方法內部:

				try {
                    //其中的singletonFactory調用getObject就是lambda表達式返回的exposedObject,也就是里面的bService還是$Proxy$
					singletonObject = singletonFactory.getObject();
					//標記為新的單例bean
                    newSingleton = true;
				}

最后我們看看,this.singletonObjects中的AService
image.png
可以看到用bService中注入了一個神秘的$Proxy$,然后寫入了一級緩存中,經調試后發現是在getSingleton方法中,調用addSingleton方法寫入的,這時候二級、三級緩存全程都沒寫入過數據。

				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}

三、回到test()方法:

    @Test
    public void test(){
        aService.zaWaLuDo();
    }

此時,aService中的bService還是個&Proxy$
image.png
這時候繼續就會正常執行aService.zaWaLuDo_()_,程序正常結束。

總結下就是這種情況下,aService會由doCreateBean方法創建,而bService是某種代理的東西存在其中。

四、我們改寫一下兩個Service,使AService需要調用BService的方法:

  1. AServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class AServiceImpl implements AService {

    @Autowired
    @Lazy
    public BService bService;

    @Override
    public void zaWaLuDo(){
        System.out.println("ToKiOToMaLei!");
        bService.starPuLaXin();
    }
}

  1. BServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BServiceImpl implements BService {
    @Autowired
    public AService aService;

    @Override
    public void starPuLaXin() {
        System.out.println("Za WaLuDo!");
    }
}

我們先在執行aServuce,zaWaLuDo()之前打個斷點看看此時的aService是什么情況:
image.png
image.png
可以看到跟上面的情況是一樣的。

這個時候我們在org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法打個斷點看看,這時候會進入org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法,執行:

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);

sharedInstance是這樣的:
image.png
里面的bService還是一個$Proxy$,我們一直調試到getBean方法結束,他最終會進入到jdk代理org.springframework.aop.framework.JdkDynamicAopProxy中執行方法:

//方法參數
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
//target根據參數argsToUse執行方法method的結果
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);

於是就會執行結果:

ToKiOToMaLei!
Za WaLuDo!

五、研究一下aServicebService的注入過程,二者都會進入doCreateBean方法,aService會入上面的過程一樣被創建,我們研究一下bService的創建過程,當執行到:

		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

執行完populateBean方法,exposedObject(即將成型的bService)就被注入了aService
image.png
但是這個aService中的bService實際上還只是個$Proxy$,在接下來的過程中,aService中的bService一直都還只是個$Proxy$,就像bService中的aService也一直都還是個$Proxy$,所以可以推斷,這種情況下Spring不關心二者是否真的存了個“成品”對象,只是有個“半成品”對象利用通過jdk動態代理AOP執行bService的方法而已, 最終會到:org.springframework.aop.framework.JdkDynamicAopProxy#invoke中執行bService的方法.

情況二:構造器注入循環依賴示例

  • com.example.demo.service.CService
package com.example.demo.service;

public interface CService {
    void goldExperience();
}

  • com.example.demo.service.DService
package com.example.demo.service;

public interface DService {
}

  • com.example.demo.service.impl.CServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.CService;
import com.example.demo.service.DService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CServiceImpl implements CService {

    private DService dService;

    @Autowired
    public CServiceImpl(DService dService) {
        this.dService = dService;
    }

    @Override
    public void goldExperience() {
        System.out.println("MUDAMUDAMUDAMUDA!!!!");
    }
}
  • com.example.demo.service.impl.DServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.CService;
import com.example.demo.service.DService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DServiceImpl implements DService {
    private CService cService;

    @Autowired
    public DServiceImpl(CService cService) {
        this.cService = cService;
    }
}

  • com.example.demo.service.CServiceTest
package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class CServiceTest {
    @Autowired
    CService cService;

    @Test
    public void test(){
        cService.goldExperience();
    }
}

運行測試方法,同樣報循環依賴的錯誤。

解決方法

在參數里添加@Lazy方法:

package com.example.demo.service.impl;

import com.example.demo.service.CService;
import com.example.demo.service.DService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class CServiceImpl implements CService {

    private DService dService;

    @Autowired
    public CServiceImpl(@Lazy DService dService) { //參數上添加了@Lazy方法
        this.dService = dService;
    }

    @Override
    public void goldExperience() {
        System.out.println("MUDAMUDAMUDAMUDA!!!!");
    }
}

源碼分析

跟情況一一樣,也是通過注入一個"假"的對象解決:
image.png
將代碼改成情況一的調用dService的方法也是通過jdk動態代理AOP解決。

Springboot解決循環依賴的源碼閱讀

application.yml中開啟:

spring:
  main:
    allow-circular-references: true

我們先調試一下看看:
image.png
發現cService中的dService是貨真價實的

我們嘗試調試看看能不能搞清楚Springboot到底是怎么注入的,在老地方org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)打個斷點,研究一下dService是怎么做的:

	@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

一直調試到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor才終於有點眉目:

			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}

此時的各變量是這樣的:
image.png
field.set(bean, value)方法內部是getFieldAccessor_(_obj_)_.set_(_obj, value_)_;后面的set_(_obj, value_)_就是已編譯好的字節碼了,執行下一步后,dService中缺乏的cService就有東西了,所以可以推測是一個寫入過程。

我們看看其巨長無比的調用棧:
image.png
在這句所在的inject方法頭部打個注解,看看有沒有頭緒,cService是從哪來的,重新啟動一下
image.png
當創建dService時,會進入到該方法體,初始的時候value啥也沒有,接着會進到:

...
	value = resolveFieldValue(field, bean, beanName); //進入本句
...
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }

一直調用到org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate方法:

	public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
			throws BeansException {

		return beanFactory.getBean(beanName); //beanName="CServiceImpl"
	}

此時由進入到了老朋友org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法中,返回了一個CServiceImpl對象給上面的resolveFieldValue(field, bean, beanName);接着就進入到field.set(bean, value);中將其注入,那么神奇的事情肯定是發生在beanFactory.getBean(beanName);

老辦法,再打個斷點,回到取CServiceImpl對象的時候看看:
image.png
接着他會進入老朋友org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法中,我們step into,在執行完Object sharedInstance = getSingleton(beanName)后就有了CServiceImpl對象,只不過他的dService還是null:

protected <T> T doGetBean(
			String name, @Nullable Class<T> 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);
    ....

最后還是會field.set(bean, value);dService先注入。

看到這里感覺非常混亂,感覺還是按那幅圖來看吧:
image.png

  1. 在創建cService調用doCreateBean方法,執行了addSingletonFactory
{
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

三級緩存this.singletonFactories 中便存入了“半成品”對象的自己:
image.png

  1. cService執行到populateBean的時候,旋即進入到了dServicedoCreateBean
  2. dService通過addSingletonFactory也往三級緩存this.singletonFactories 中便存入了“半成品”對象的自己,此時c、d都在三級緩存this.singletonFactories里:
    image.png
  3. dService執行了下一句,即populateBean之后,cService從三級緩存換入到了二級緩存this.earlySingletonObjects
    image.png
    此時其內部的dService為空:
    image.png
  4. 到這一步,我們再理清一下調用鏈:c-d.png
    dService執行到getSingleton后:
// Create bean instance.
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }

getSingleton內部執行了addSingleton_(_beanName, singletonObject_)_之后,便把自己寫入了三級緩存this.singletonObjects中,並把半成品的cService注入到自己中,形如:image.png

  1. 之后就回到了cService->populateBean的執行,最終去到了field.set_(_bean, value_)_中,此時bean為cService, value為dService(內部的cServicedService仍未空),執行完之后,就鏈接上了!神奇!:image.png
  2. cService ->populateBean執行結束后,回到cService -> doGetBean的執行,進行完cService -> getSingleton后,二級緩存this.earlySingletonObjects中的cService也移入了一級緩存this.singletonObjects之中:image.png
    此時,基本上解決了循環引用的問題。

總結一下

  1. 我們認為一個對象的創建可分為兩步:
    1. 實例化:可認為是一個在內存中划分空間的過程
    2. 初始化:為該對象的屬性賦值的過程
  2. cService在創建的時候,先只進行了實例化,此時是一個“半成品”對象,寫入三級緩存中存儲;
  3. 旋即進行其所需要的dService對象的創建,而dService會進行實例化之后,也將“半成品”的自己寫入三級緩存中,
  4. 此時cService會嘗試進行初始化(為屬性賦值),他需要寫入dService,但事實上會為dService賦值為null,然后寫入二級緩存,此時的cService仍然是個“半成品”。
  5. 接着又回到dService的創建,這時候他也會進行一個初始化,將二級隊列中的完成了“假”的初始化的“半成品”對象cService,給自己所需的屬性注入,完成了初始化過程,並寫入了一級緩存。
  6. 然后就回到了cService還在進行中的創建過程,這個時候cService是個“假”的“半成品”,在二級緩存,而dService是個真的成品,他確實擁有了cService對象。
  7. cService這時候也會將一級緩存中的dService,一個真正完成了初始化的對象,注入到自己的屬性中,這個時候二者終於完成了互相注入,cService也完成了初始化,進入了一級緩存,循環依賴得以解決。

為什么需要三級緩存?

我大概總結了一下流程:
3.png
可以看出,互相依賴的兩個對象有三種狀態:

  1. 只有“存在”,沒有內部的“半成品”形態一對象
  2. 注入了“半成品”形態一對象的“半成品”形態二對象
  3. 注入了“半成品”形態二對象的完全體“成品”對象

因有客觀上有三種狀態的對象,所以才利用三級緩存來分別存儲,比較科學的說明如下:

三級緩存

singletonObjects

一級緩存, Cache of singleton objects bean name --> bean instance。 存放完整對象。

earlySingletonObjects

二級緩存, Cache of early singleton objects bean name --> bean instance 提前曝光的BEAN緩存。 存放半成品對象。

singletonFactories

三級緩存, Cache of singleton factories bean name --> ObjectFactory。需要的對象被代理時,就必須使用三級緩存(否則二級就夠了)。解決循環依賴中存在aop的問題 存放 lambda 表達式和對象名稱的映射。

參考

  1. https://www.zhihu.com/question/438247718
  2. https://blog.csdn.net/qq_18298439/article/details/88818418
  3. https://www.zhihu.com/question/438247718/answer/2331910821

本文僅為分享學習經歷,歡迎批評指正


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM