最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深度解析SpringBoot中@Async引起的循環(huán)依賴

 更新時間:2022年02月09日 16:40:46   作者:程銘程銘你快成名  
本文主要介紹了深度解析SpringBoot中@Async引起的循環(huán)依賴,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

啊,昨晚發(fā)版又出現(xiàn)了讓有頭大的循環(huán)依賴問題,按理說Spring會為我們解決循環(huán)依賴,但是為什么還會出現(xiàn)這個問題呢?為什么在本地、UAT以及PRE環(huán)境都沒有出現(xiàn)這個問題,但是到了PROD環(huán)境就出現(xiàn)了這個問題呢?本文將從事故時間線、及時止損、復盤分析等幾個方面為大家?guī)碓敿毜姆治?,干貨滿滿!

事故時間線

本著"先止損、后復盤分析"的原則,我們來看一下這次發(fā)版事故的時間線。

2021年11月16日晚23點00分00秒開始發(fā)版,此時集團的devops有點慢

2021年11月16日晚23點03分01秒,收到發(fā)版失敗的消息,登錄服務器發(fā)現(xiàn)發(fā)生了循環(huán)依賴,具體錯誤如下圖,從日志中可以看到是dataCollectionSendMessageService這個bean出現(xiàn)了循環(huán)依賴

在這里插入圖片描述

問題發(fā)現(xiàn)了就需要先解決,然后再去分析為什么??吹竭@個報錯日志我心里也大概知道是為什么了,所以很快就解決了,解決方案如下:給DataCollectionSendMessageService加上@Lazy注解

在這里插入圖片描述

2021年11月16日晚23點07分16秒,使用重新集成的代碼開始發(fā)版,大概10分鐘后線上節(jié)點全部發(fā)版完成。從時間線來看從發(fā)現(xiàn)問題到解決問題,前后一共用了接近15分鐘(這期間代碼集成和發(fā)布用了過多的時間),也算是做到了及時止損,沒有讓問題繼續(xù)擴大。

猜想

我大膽的猜想是因為打了@Aysnc注解的bean生成了對象的代理,導致Spring bean最終加載的不是一個原始對象導致了此次問題的發(fā)生,那么對不對呢,接下來我們通過源碼詳細分析一下。

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

所謂循環(huán)依賴就是Spring IOC容器在加載bean時會按照順序加載,先去實例化 beanA。然后發(fā)現(xiàn) beanA 依賴于 beanB,接在又去實例化 beanB。實例化 beanB 時,發(fā)現(xiàn) beanB 又依賴于 beanA。如果容器不處理循環(huán)依賴的話,容器會無限執(zhí)行上面的流程,直到內存溢出,程序崩潰,所以這個時候就會拋出BeanCurrentlyInCreationException異常,也就是我們常說的循環(huán)依賴,下面是兩種常見循環(huán)依賴的場景。

幾個Bean之間的循環(huán)依賴

@Component
public class A {

    @Autowired
    private B b;
}

@Component
public class B {

    @Autowired
    private C c;
}

@Component
public class C {

    @Autowired
    private A a;
}

效果圖如下:

在這里插入圖片描述

自己依賴自己

@Component
public class A {

    @Autowired
    private A a;
}

效果圖如下:

在這里插入圖片描述

Spring是如何解決循環(huán)依賴的

在這里插入圖片描述

首先Spring維護了三個Map,也就是我們通常說的三級緩存

  • singletonObjects:俗稱單例池,緩存創(chuàng)建完成的單例Bean
  • singletonFactories:映射創(chuàng)建Bean的原始工廠
  • earlySingletonObjects:映射Bean的早期引用,也就是說這個Map里的Bean不是完整的,只是完成了實例化,但還沒有初始化

Spring通過三級緩存解決了循環(huán)依賴,其中一級緩存為單例池(singletonObjects),二級緩存為早期曝光對象earlySingletonObjects,三級緩存為早期曝光對象工廠(singletonFactories)。

當A、B兩個類發(fā)生循環(huán)引用時,在A完成實例化后,就使用實例化后的對象去創(chuàng)建一個對象工廠,并添加到三級緩存中,如果A被AOP代理,那么通過這個工廠獲取到的就是A代理后的對象,如果A沒有被AOP代理,那么這個工廠獲取到的就是A實例化的對象。

當A進行屬性注入時,會去創(chuàng)建B,同時B又依賴了A,所以創(chuàng)建B的同時又會去調用getBean(a)來獲取需要的依賴,此時的getBean(a)會從緩存中獲取,第一步,先獲取到三級緩存中的工廠;第二步,調用對象工工廠的getObject方法來獲取到對應的對象,得到這個對象后將其注入到B中。

緊接著B會走完它的生命周期流程,包括初始化、后置處理器等。當B創(chuàng)建完后,會將B再注入到A中,此時A再完成它的整個生命周期。至此,循環(huán)依賴結束!

簡單一句話說:先去緩存里找Bean,沒有則實例化當前的Bean放到Map,如果有需要依賴當前Bean的,就能從Map取到。

什么是@Async

@Async注解是Spring為我們提供的異步調用的注解,@Async可以作用到類或者方法上,標記了@Async注解的方法將會在獨立的線程中被執(zhí)行,調用者無需等待它的完成,即可繼續(xù)其他的操作。從源碼中可以看到標記了@Async注解的方法會被提交到org.springframework.core.task.TaskExecutor中異步執(zhí)行。

在這里插入圖片描述


或者我們可以通過value來指定使用哪個自定義線程池,比如這樣子:

@Async("asyncTaskExecutor")

被@Async標記的bean注入時機

我們從源碼的角度來看一下被@Async標記的bean是如何注入到Spring容器里的。在我們開啟@EnableAsync注解之后代表可以向Spring容器中注入AsyncAnnotationBeanPostProcessor,它是一個后置處理器,我們看一下他的類圖。

在這里插入圖片描述

真正創(chuàng)建代理對象的代碼在AbstractAdvisingBeanPostProcessor中的postProcessAfterInitialization方法中,以下代碼有所刪減,只保留核心邏輯代碼

	// 這個map用來緩存所有被postProcessAfterInitialization這個方法處理的bean
	private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);

	// 這個方法主要是為打了@Async注解的bean生成代理對象
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		// 這里是重點,這里返回true
		if (isEligible(bean, beanName)) {
			// 工廠模式生成一個proxyFactory
			ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
			if (!proxyFactory.isProxyTargetClass()) {
				evaluateProxyInterfaces(bean.getClass(), proxyFactory);
			}
			// 切入切面并創(chuàng)建一個代理對象
			proxyFactory.addAdvisor(this.advisor);
			customizeProxyFactory(proxyFactory);
			return proxyFactory.getProxy(getProxyClassLoader());
		}
		// No proxy needed.
		return bean;
	}
	protected boolean isEligible(Class<?> targetClass) {
		// 首次從eligibleBeans這個map中一定是拿不到的
		Boolean eligible = this.eligibleBeans.get(targetClass);
		if (eligible != null) {
			return eligible;
		}
		// 如果沒有advisor,也就是切面,直接返回false
		if (this.advisor == null) {
			return false;
		}
		// 這里判斷AsyncAnnotationAdvisor能否切入,因為我們的bean是打了@Aysnc注解,這里是一定能切入的,最終會返回true
		eligible = AopUtils.canApply(this.advisor, targetClass);
		this.eligibleBeans.put(targetClass, eligible);
		return eligible;
	}

至此打了@Aysnc注解的bean就創(chuàng)建完成了,結果是生成了一個代理對象

循環(huán)依賴到底是怎么生成的

經(jīng)過上面的源碼分析,我們可以知道有@Aysnc注解的bean最后生成了一個代理對象,我們結合Spring bean創(chuàng)建的流程來分析這次問題。

  • beanA開始初始化,beanA實例化完成后給beanA的依賴屬性beanB進行賦值
  • beanB開始初始化,beanB實例化完成后給beanB的依賴屬性beanA進行賦值
  • 因為beanA是支持循環(huán)依賴的,所以可以在earlySingletonObjects中可以拿到beanA的早期引用的,但是因為beanB打了@Aysnc注解并不能在earlySingletonObjects中可以拿到早期引用
  • 接下來執(zhí)行執(zhí)行initializeBean(Object existingBean, String beanName)方法,這里beanA可以正常實例化完成,但是因為beanB打了@Aysnc注解,所以向Spring IOC容器中增加了一個代理對象,也就是說beanAbeanB并不是一個原始對象,而是一個代理對象
  • 接下來進行執(zhí)行doCreateBean方法時對進行檢測,以下代碼有所刪減,只保留核心邏輯代碼
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					// 重點在這里,這里會遍歷所有依賴的bean,如果beanA依賴beanB和緩存中的beanB不相等
					// 也就是說beanA本來依賴的是一個原始對象beanB,但是這個時候發(fā)現(xiàn)beanB是一個代理對象,就會增加到actualDependentBeans
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					// 發(fā)現(xiàn)actualDependentBeans不為空,就發(fā)生了我們最開始截圖的錯誤
					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.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

解決循環(huán)依賴的正確姿勢

  • @Lazy注解
  • 代碼優(yōu)化,不要讓@Async的Bean參與循環(huán)依賴

至此我們就知道為什么發(fā)生了此次問題。

到此這篇關于深度解析SpringBoot中@Async引起的循環(huán)依賴的文章就介紹到這了,更多相關SpringBoot中@Async循環(huán)依賴內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 解決Spring?AOP攔截抽象類(父類)中方法失效問題

    解決Spring?AOP攔截抽象類(父類)中方法失效問題

    這篇文章主要介紹了解決Spring?AOP攔截抽象類(父類)中方法失效問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java web開發(fā)中加載圖片路徑的兩種方式

    Java web開發(fā)中加載圖片路徑的兩種方式

    下文給大家介紹基于編譯器idea以及tomcat服務器開發(fā)的,對Java web開發(fā)加載圖片路徑的兩種方式感興趣的朋友一起看看吧
    2017-07-07
  • Nett中的心跳機制與斷線重連詳解

    Nett中的心跳機制與斷線重連詳解

    這篇文章主要介紹了Nett中的心跳機制與斷線重連詳解,我們以客戶端發(fā)送心跳為例,平時我們的心跳實現(xiàn)方式可能是搞個定時器,定時發(fā)送是吧,但是在Netty中卻不一樣,心跳被稱為空閑檢測,需要的朋友可以參考下
    2023-12-12
  • PowerJob的QueryConvertUtils工作流程源碼解讀

    PowerJob的QueryConvertUtils工作流程源碼解讀

    這篇文章主要為大家介紹了PowerJob的QueryConvertUtils工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • Springboot關于自定義stater的yml無法提示問題解決方案

    Springboot關于自定義stater的yml無法提示問題解決方案

    這篇文章主要介紹了Springboot關于自定義stater的yml無法提示問題及解決方案,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • java開發(fā)時各類工具的使用規(guī)范

    java開發(fā)時各類工具的使用規(guī)范

    這篇文章主要介紹了java編碼時各類工具的使用規(guī)范,多人協(xié)作、共同開發(fā)一個項目,如果沒有統(tǒng)一的代碼規(guī)范的話,項目中的每個人都按照自己的習慣率性而為,就會導致整個項目的代碼看上去雜亂無章,可讀性非常差,并且持續(xù)增加后續(xù)的維護成本。對此感興趣可以來了解一下
    2020-07-07
  • SpringBoot如何自定義線程池配置類

    SpringBoot如何自定義線程池配置類

    有時候我們在項目中做一些長鏈路的跑批任務時,基于Springboot項目的定時任務,我們可以指定一個自定義的線程配置類進行單獨提供給具體跑批任務使用,而不占用整個系統(tǒng)資源,這篇文章主要介紹了SpringBoot如何自定義線程池配置類,需要的朋友可以參考下
    2024-04-04
  • 親身體驗Intellij?Idea從卡頓到順暢全過程

    親身體驗Intellij?Idea從卡頓到順暢全過程

    這篇文章主要介紹了親身體驗Intellij?Idea從卡頓到順暢全過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • JDK的具體安裝步驟(帶圖帶解釋巨詳細)

    JDK的具體安裝步驟(帶圖帶解釋巨詳細)

    Java是一種廣泛使用的編程語言,許多應用程序和系統(tǒng)都依賴于它,如果您想進行Java編程或運行Java應用程序,首先需要安裝Java開發(fā)工具包(JDK),這篇文章主要給大家介紹了關于JDK具體安裝步驟的相關資料,文中介紹的方法帶圖帶解釋巨詳細,需要的朋友可以參考下
    2024-05-05
  • Redis高并發(fā)場景防止庫存數(shù)量超賣少賣

    Redis高并發(fā)場景防止庫存數(shù)量超賣少賣

    商品超賣是銷售數(shù)量超過實際庫存的情況,常因庫存管理不當引發(fā),傳統(tǒng)庫存管理在高并發(fā)環(huán)境下易出錯,可通過線程加鎖或使用Redis同步庫存狀態(tài)解決,本文就來詳細的介紹一下,感興趣的可以了解一下
    2024-09-09

最新評論

策勒县| 合阳县| 濮阳县| 聊城市| 惠东县| 响水县| 麻阳| 红原县| 安平县| 永嘉县| 灵川县| 水富县| 肇东市| 积石山| 乌拉特中旗| 广平县| 富平县| 娱乐| 福泉市| 军事| 杭锦后旗| 登封市| 隆昌县| 治多县| 宿松县| 二连浩特市| 荣成市| 日喀则市| 镇安县| 娄底市| 汉阴县| 漯河市| 宁河县| 中阳县| 鲜城| 社会| 嵊泗县| 定日县| 土默特右旗| 津南区| 东丽区|