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

Java @Async注解導(dǎo)致spring啟動失敗解決方案詳解

 更新時間:2021年09月02日 09:48:18   作者:liangsheng_g  
這篇文章主要介紹了Java @Async注解導(dǎo)致spring啟動失敗解決方案詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下

前言

這篇文章里,最后總結(jié)處,我說了會講講循環(huán)依賴中,其中一個類添加@Async有可能會導(dǎo)致注入失敗而拋異常的情況,今天就分析一下。

一、異常表現(xiàn),拋出內(nèi)容

1.1循環(huán)依賴的兩個class

1.CycleService1

@Service
public class CycleService1 {

	@Autowired
	private CycleService2 cycleService2;

	@WangAnno
	@Async
	public void doThings() {
		System.out.println("it's a async move");
	}

}

2.CycleService2

@Service
public class CycleService2 {

	private CycleService1 cycleService1;

	public void init() {

	}

	@WangAnno
	public void alsoDo() {
		System.out.println("create cycleService2");
	}

}

1.2 啟動報錯

Bean with name ‘cycleService1' has been injected into other beans [cycleService2] 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.

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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.

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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.

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:654)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:320)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:851)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:884)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:552)

at com.wang.Test.main(Test.java:109)

二、原因分析

2.1 主要原因

想想我在這篇博客里的圖解步驟:

  1. 當Spring在進行bean的實例化的時候,由于CycleService1和CycleService2是循環(huán)依賴的,
  2. 同時,由于CycleService1創(chuàng)建早于CycleService2。
  3. 所以,在CycleService1對CycleService2的initializeBean方法執(zhí)行之后得到了exposedObject,要從二級緩存里獲取CycleService1的earlySingletonReference不為null,就需要比較exposedObject和raw CycleService是否還是同一個對象,如果不再是同一個對象,那么就會報錯。
  4. 為什么有這個邏輯呢?
  5. 其實是因為如果能從二級緩存里拿出的earlySingletonReference不為null,說明了在該對象再創(chuàng)建過程中被其他對象循環(huán)依賴了,且調(diào)用了三級工廠中該對象的ObjectFactory方法,基于raw bean生成了對象放入到了二級緩存。但是當raw bean執(zhí)行完initializeBean之后生成了新的對象,那就出問題了。如下圖:

在這里插入圖片描述

也就是說基于raw bean,得到了兩個基于該raw bean生成的proxy對象,Spring容器不知道最終該在容器里保存哪一個了。

2.2 循環(huán)依賴放入二級緩存處邏輯

1.每個bean在進行屬性注入之前,默認都會往Spring容器中放入一個ObjectFactory進入三級工廠,以便自己在屬性注入的時候被循環(huán)依賴時調(diào)用生成對象

if (earlySingletonExposure) {
	// 返回一個進行了aop處理的ObjectFactory,提前暴露
	// 但是只有當該實例在創(chuàng)建過程中還被其他實例引用(循環(huán)依賴),才會被調(diào)用getEarlyBeanReference
	// 此處是第四次調(diào)用beanPostProcessor,不一定會調(diào)用,只有當該類真的在創(chuàng)建過程中被其他類當做屬性所依賴
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2.所以在創(chuàng)建CycleService1過程中,CycleService2去注入CycleService2之前在三級工廠里放入了自己的ObjectFactory對象,然后在CycleService2創(chuàng)建過程中,要注入CycleService1的時候,就會調(diào)用Spring容器中的getEarlyBeanReference(beanName, mbd, bean)獲取CycleService1,下面我們來看看該方法調(diào)用的具體邏輯

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

在這里插入圖片描述

然后咱們debug發(fā)現(xiàn),只有AbstractAutoProxyCreator#getEarlyBeanReference方法,有具體實現(xiàn)邏輯

public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

具體的細節(jié),我們就不進入的,主要就是通過調(diào)用wrapIfNecessary生成了raw bean的aop proxy bean,后面放入了二級緩存。

2.3 initializeBean生成的對象

在initializeBean方法里會調(diào)用applyBeanPostProcessorsAfterInitialization方法

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

在循環(huán)里面會調(diào)用postProcessAfterInitialization方法
重點關(guān)注AbstractAutoProxyCreator的該方法實現(xiàn):

@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// 對bean進行proxy操作
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

會發(fā)現(xiàn)AbstractAutoProxyCreator#postProcessAfterInitialization里面的具體邏輯就是判斷這個類有沒有調(diào)用過wrapIfNecessary,如果調(diào)用過就不再調(diào)用,就是保證同一個raw bean不會被多次proxy,同時提前暴露注入到其他對象里的就是proxy bean。
但是由于該bean(CycleService1)上加了@Async注解,此次也會觸發(fā)AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization,而這個方法,我們在這篇文章里講過了,正是@Async注解能生效的關(guān)鍵邏輯。所以此處生成了一個具有Async功能的新的async proxy bean

2.4 再次分析原因

基于2.3和2.4,我們基于raw bean得到了二級緩存里的aop proxy bean和async proxy bean。
讓我們再回憶一下判斷邏輯:

//此處是從二級緩存里面根據(jù)beanName拿出對象,因為二級緩存里放入的是因為循環(huán)依賴給其他bean注入的代理對象
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		// 我們之前早期暴露出去的Bean跟現(xiàn)在最后要放到容器中的Bean不是同一個
		// allowRawInjectionDespiteWrapping為false
		// 并且當前Bean被當成依賴注入到了別的Bean中
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			// 獲取到當前Bean依賴的Bean
			String[] dependentBeans = getDependentBeans(beanName);
			// 要得到真實的依賴的Bean
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				// 移除那些僅僅為了類型檢查而創(chuàng)建出來
				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.");
			}
		}
	}

簡而言之,也就是此時從二級緩存里拿到了aop proxy bean,同時了執(zhí)行完initializeBean之后,raw bean變?yōu)榱薬sync proxybean,Spring容器基于raw bean得到了兩個proxy bean,無法處理了。所以在使用@Async注解時,盡量不要在被循環(huán)依賴的Class上添加

解決方案

打破循環(huán)依賴

目前我能想到的方法就是打破循環(huán)依賴,因為循環(huán)依賴發(fā)生在bean生命周期的–屬性注入階段,所以我們需要做的就是打破這種循環(huán)依賴

1.延遲注入(使用@Lazy注解)

@Service
public class CycleService1 {

	@Lazy
	@Autowired
	private CycleService2 cycleService2;

	@WangAnno
	@Async
	public void doThings() {
		cycleService2.alsoDo();
		System.out.println("it's a async move");
	}

}

看過這篇文章的都知道原理了,此處不再累贅

2. 手動延遲注入(使用applicationContext.getBean)

@Service
public class CycleService1 {

	@Autowired
	private ApplicationContext applicationContext;

	private CycleService2 cycleService2;

	@WangAnno
	@Async
	public void doThings() {
		if (Objects.isNull(cycleService2)) {
			cycleService2 = applicationContext.getBean(CycleService2.class);
		}
		cycleService2.alsoDo();
		System.out.println("it's a async move");
	}

}

其實效果是上面加了@Lazy效果是一樣的,不過是我們自己在方法執(zhí)行的過程中手動進行延遲注入而已。

總結(jié)

在這里插入圖片描述

從二級緩存里拿到earlySingletonReference(aop proxy bean),同時了執(zhí)行完initializeBean之后,raw bean變?yōu)榱薳xposedObject(async proxy bean),Spring容器基于raw bean得到了兩個proxy bean,無法處理了。
所以在使用@Async注解時,盡量不要在被循環(huán)依賴的Class上添加
實在非要添加,可以看看我給出的解決方法。

到此這篇關(guān)于Java @Async注解導(dǎo)致spring啟動失敗解決方案詳解的文章就介紹到這了,更多相關(guān)Java @Async注解導(dǎo)致spring啟動失敗解決方案內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java多態(tài)中的向上轉(zhuǎn)型與向下轉(zhuǎn)型淺析

    Java多態(tài)中的向上轉(zhuǎn)型與向下轉(zhuǎn)型淺析

    多態(tài)是指不同類的對象在調(diào)用同一個方法是所呈現(xiàn)出的多種不同行為,下面這篇文章主要給大家介紹了關(guān)于Java多態(tài)中向上轉(zhuǎn)型與向下轉(zhuǎn)型的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-02-02
  • java實現(xiàn)撲克牌發(fā)牌器

    java實現(xiàn)撲克牌發(fā)牌器

    這篇文章主要為大家詳細介紹了java實現(xiàn)撲克牌發(fā)牌器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • SpringBoot+WebSocket實現(xiàn)即時通訊功能(J2EE方式)

    SpringBoot+WebSocket實現(xiàn)即時通訊功能(J2EE方式)

    WebSocket是一種在單個TCP連接上進行全雙工通信的協(xié)議,WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù),本文給大家介紹了SpringBoot+WebSocket實現(xiàn)即時通訊功能(J2EE方式),需要的朋友可以參考下
    2025-01-01
  • Java使用正則獲取括號里面的內(nèi)容

    Java使用正則獲取括號里面的內(nèi)容

    這篇文章主要介紹了Java使用正則獲取括號里面的內(nèi)容問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Java實現(xiàn)批量修改文件名和重命名的方法

    Java實現(xiàn)批量修改文件名和重命名的方法

    這篇文章主要介紹了Java實現(xiàn)批量修改文件名和重命名的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • Java中使用正則檢查有效日期的實現(xiàn)

    Java中使用正則檢查有效日期的實現(xiàn)

    要判斷一個字符串是否符合時間格式,可以使用正則表達式,本文主要介紹了Java中使用正則檢查有效日期的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • Android開發(fā)中實現(xiàn)用戶注冊和登陸的代碼實例分享

    Android開發(fā)中實現(xiàn)用戶注冊和登陸的代碼實例分享

    這篇文章主要介紹了Android開發(fā)中實現(xiàn)用戶注冊和登陸的代碼實例分享,只是實現(xiàn)基本功能,界面華麗度就請忽略啦XD 需要的朋友可以參考下
    2015-12-12
  • 詳解java nio中的select和channel

    詳解java nio中的select和channel

    這篇文章主要介紹了java nio中的select和channel
    2019-05-05
  • 淺談fastjson的常用使用方法

    淺談fastjson的常用使用方法

    下面小編就為大家?guī)硪黄獪\談fastjson的常用使用方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • 詳解SpringBoot如何讓指定的Bean先加載

    詳解SpringBoot如何讓指定的Bean先加載

    這篇文章主要給大家介紹了在 SpringBoot 中如何讓自己的某個指定的 Bean 在其他 Bean 前完成被 Spring 加載,文中通過代碼示例給大家講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2024-06-06

最新評論

大名县| 门源| 东莞市| 玉山县| 东源县| 黔南| 赫章县| 周口市| 清流县| 丹阳市| 涞源县| 方正县| 皮山县| 濮阳市| 武安市| 夏津县| 故城县| 梅州市| 策勒县| 乐安县| 渭南市| 金坛市| 嵩明县| 运城市| 宜阳县| 曲靖市| 保亭| 丽水市| 富源县| 和田市| 葫芦岛市| 彭山县| 司法| 泰和县| 镇江市| 安丘市| 临沧市| 怀集县| 新宁县| 巴彦淖尔市| 彭山县|