Spring使用@Async出現(xiàn)循環(huán)依賴原因及解決方案分析
場景復(fù)現(xiàn)
1、首先項(xiàng)目需要打開spring的異步開關(guān),在application主類上加@EnableAsync
2、創(chuàng)建一個包含了@Async方法的異步類MessageService:
@Service public class MessageService { @Resource private TaskService taskService; @Async public void send(){ taskService.shit(); } }
3、創(chuàng)建另一個正常類TaskService,與異步類形成循環(huán)引用的關(guān)系(注意MessageService和TaskService在同一個包內(nèi),并且order為默認(rèn),因此會先掃描MessageService再掃描TaskService):
@Service public class TaskService { @Resource private MessageService messageService; public void shit(){ System.out.println(); } }
4、啟動springboot項(xiàng)目成功報(bào)錯
問題出現(xiàn)的原因
在分析原因之前,我們需要提前知道兩個重要的點(diǎn):
- spring的aop代理(包括@Transactional 事務(wù)代理),都是在AbstractAutowireCapableBeanFactory的populateBean方法后的initializeBean當(dāng)中的applyBeanPostProcessorsAfterInitialization方法里,通過特定的后置處理器里創(chuàng)建代理對象(如果用@Autowired則是AnnotationAwareAspectJAutoProxyCreator)
- 然而1.當(dāng)中描述的代理過程,是在這個類不涉及到循環(huán)引用的情況下才會執(zhí)行,也就是說滿足百分之90的情況,而循環(huán)引用的情況會做特殊的處理,即提前創(chuàng)建代理對象。
舉個例子: T類是個包含了@Transactional方法的類,屬于需要被代理的對象,并且通過@Resource(或者@Autowired)的方式依賴了A ,A類中也以同樣的方式注入了T,并且T類先于A類開始實(shí)例化過程,那么簡單的實(shí)例化流程就是:
- T的BeanDefinition被spring拿到后,根據(jù)構(gòu)造器實(shí)例化一個T對象(原始對象而非代理對象),并包裝成objectFactory放入singletonFactories(三級緩存)中 然后執(zhí)行populateBean方法開始注入屬性的流程,其中會利用CommonAnnotationBeanPostProcessor(@Resource用這個后置處理器,@Autowired用 AutowiredAnnotationBeanPostProcessor)執(zhí)行T的屬性注入步驟,遍歷T中所依賴的屬性
- 發(fā)現(xiàn)T依賴了A,會先到beanFactory的一至三級緩存中,通過A的beanName查詢A對象,如果沒找到,即A還沒有被實(shí)例化過,那么會將A作為實(shí)例化的目標(biāo),重復(fù)a.步驟:將A實(shí)例化后的對象包裝成objectFactory放入singletonFactories,接著對A執(zhí)行populateBean來注入屬性
- 遍歷A的屬性,發(fā)現(xiàn)A依賴了T,然后嘗試去beanFactory中獲取T的實(shí)例,發(fā)現(xiàn)三級緩存中存在T的objectFactory,因此執(zhí)行objectFactory.getObject方法企圖獲取T的實(shí)例。然而這個objectFactory并非是簡單把對象返回出去,而是在當(dāng)初包裝的時候,就將AbstractAutowireCapableBeanFactory的getEarlyBeanReference方法寫入getObject當(dāng)中
- 在getEarlyBeanReference方法里,會遍歷所有SmartInstantiationAwareBeanPostProcessor的子類型的后置處理器,執(zhí)行對應(yīng)的getEarlyBeanReference方法,此時會將第1.點(diǎn)提到的代理過程提前,即通過 AnnotationAwareAspectJAutoProxyCreator(SmartInstantiationAwareBeanPostProcessor的子類)來創(chuàng)建一個代理對象,并放入二級緩存earlySingletonObjects當(dāng)中,然后將這個代理對象通過field.set的形式(默認(rèn)形式)注入到A,至此就完成了普通aop對象的循環(huán)引用處理
出現(xiàn)本文標(biāo)題中循環(huán)引用異常的原因分析
包含了@Async 方法的類與@Transactional的類相似,也會被替換成一個新的代理類,但是與普通aop不同的是,@Async不會在 getEarlyBeanReference 階段執(zhí)行創(chuàng)建代理的邏輯(這么做的原因暫時沒仔細(xì)分析),而是被延遲到了initializeBean步驟當(dāng)中(即1.提到的90%的代理情況),這樣一來就會導(dǎo)致TaskService注入的并不是最終創(chuàng)建完成的MessageService的代理對象,很明顯這樣的結(jié)果是不合理的,而在代碼層面,spring的AbstractAutowireCapableBeanFactory當(dāng)中,在initializeBean和將bean放入一級緩存之間,有這么一段容易被忽視的代碼,用于把控最終的循環(huán)引用結(jié)果正確性:
//是否允許提前暴露,可以理解為是否允許循環(huán)引用 if (earlySingletonExposure) { //遍歷一到三級緩存,拿到的bean Object earlySingletonReference = getSingleton(beanName, false); //如果緩存中的對象不為空 if (earlySingletonReference != null) { //exposedObject是執(zhí)行了initializeBean之后的對象,bean是通過構(gòu)造器創(chuàng)建的原始對象 //如果兩者相等,則將exposedObject設(shè)置為緩存中的對象 if (exposedObject == bean) { exposedObject = earlySingletonReference; } //如果兩者不是同一個對象,并且不允許直接注入原生對象(默認(rèn)false),且當(dāng)前beanName有被其他的bean所依賴 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { //則獲取所有依賴了該beanName的對象 String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { //如果這個對象已經(jīng)處于一級緩存當(dāng)中,則添加到actualDependentBeans,即依賴該對象的bean是一個走完了整個流程,不會再有機(jī)會回爐重做的bean if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } //最后判斷actualDependentBeans是否為空,不為空就拋循環(huán)引用的異常 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."); } } } }
- 我們結(jié)合這段代碼來分析@Async 循環(huán)引用場景:
- 先看第4行,首先這個時候肯定還沒進(jìn)入一級緩存,而我們知道@Async在 getEarlyBeanReference 中并沒有執(zhí)行代理,因此第4行獲取到的 earlySingletonReference 是messageService的原始對象
- 進(jìn)入第9行,判斷exposedObject == bean,由于@Async的代理過程發(fā)生在initializeBean中, 因此exposedObject是代理對象,而bean是通過構(gòu)造器直接實(shí)例化的原始對象,因此肯定不相等
- 進(jìn)入第12行,allowRawInjectionDespiteWrapping默認(rèn)為false,而messageService是被TaskService所引用的,因此 hasDependentBean (beanName)為true ,會進(jìn)入14行代碼塊
- 重點(diǎn)是判斷18行的 ! removeSingletonIfCreatedForTypeCheckOnly (dependentBean),該方法代碼為:
protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) { //如果不是已經(jīng)完全創(chuàng)建好的bean,就返回true,否則返回false if (!this.alreadyCreated.contains(beanName)) { removeSingleton(beanName); return true; } else { return false; } }
這里就要回到場景復(fù)現(xiàn)時提到的:
3、注意MessageService和TaskService在同一個包內(nèi),并且order為默認(rèn),因此會先掃描MessageService再掃描TaskService。
- 由于messageService先被掃描,因此會在messageService的populateBean當(dāng)中,執(zhí)行TaskService的實(shí)例化過程,而TaskService此時并不知道m(xù)essageService是一個需要代理的類,因此將一個未代理的messageService注入之后,心安理得地執(zhí)行了initializeBean以及后續(xù)的初始化操作,然后標(biāo)記為成功創(chuàng)建并裝入一級緩存。
- 也就是說,此時spring判斷TaskService是一個已經(jīng)完全實(shí)例化并初始化完成的對象。因此removeSingletonIfCreatedForTypeCheckOnly方法會返回false,則18行返回的是true,所以TaskService會被加入到actualDependentBeans當(dāng)中,最終拋出BeanCurrentlyInCreationException異常
- 簡單來說,spring認(rèn)為如果一個bean在initializeBean前后不一致,并且一個已經(jīng)完全初始化的beanA注入了這個未完全初始化的beanB,在spring的流程中beanA就再也沒有機(jī)會改變注入的依賴了,所以會拋異常。
- 而如果先實(shí)例化TaskService再實(shí)例化MessageService,就不會有這個問題(不信可以將TaskService改成ATaskService試試),因?yàn)槿绻趯?shí)例化TaskService的時候沒有發(fā)現(xiàn)提前暴露出來的MessageService,就會專注于創(chuàng)建MessageService的過程,實(shí)例化并初始化完成后才會回到TaskService并將MessageService注入
為什么@Lazy可以解決這個問題
@Lazy 被大多數(shù)人理解為:當(dāng)使用到的時候才會加載這個類。
這個也算是spring希望我們看到的,但是這個描述實(shí)際上不完全準(zhǔn)確。舉個例子:
@Service public class TaskService { @Resource @Lazy private MessageService messageService; public void shit(){ System.out.println(); } }
- 這里在messageService屬性上面加了@Lazy。在實(shí)例化TaskService,并populateBean的時候,在 CommonAnnotationBeanPostProcessor 的 getResourceToInject方法中, spring發(fā)現(xiàn)messageService被@Lazy注解修飾,便會將其包裝成一個代理對象:即創(chuàng)建一個TargetSource,重寫getTarget方法,返回的是 CommonAnnotationBeanPostProcessor 里的 getResource(beanName)方法(方法體中的邏輯,可以理解為從工廠的三層緩存中獲取對象)。也就是說,注入給TaskService的是一個MessageService的代理對象(這是本文出現(xiàn)的第三種代理場景)。
- 而spring在實(shí)例化MessageService的時候,不會管他是否是由@Lazy 修飾的,只會將其當(dāng)做一個普通的bean去創(chuàng)建,成功后就會放入一級緩存(所以嚴(yán)格來講,不能說是“使用到了再去加載”)。
- 容器啟動完成后,TaskService在需要使用messageService的方法時,會執(zhí)行代理對象的邏輯,獲取到TargetSource,調(diào)用getResource從三層緩存中獲取messageService的真實(shí)對象,由于messageService此時已經(jīng)被spring完整地創(chuàng)建好了,處于一級緩存singletonObjects當(dāng)中,因此拿到之后可以放心使用。
到此這篇關(guān)于Spring使用@Async出現(xiàn)循環(huán)依賴原因以及解決方案的文章就介紹到這了,更多相關(guān)Spring @Async循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot Nacos實(shí)現(xiàn)自動刷新
這篇文章主要介紹了SpringBoot Nacos實(shí)現(xiàn)自動刷新,Nacos(Dynamic Naming and Configuration Service)是阿里巴巴開源的一個動態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)管理平臺2023-01-01Java兩個乒乓球隊(duì)比賽名單問題(判斷素?cái)?shù))
兩個乒乓球隊(duì)進(jìn)行比賽,各出三人。甲隊(duì)為a,b,c三人,乙隊(duì)為x,y,z三人。已抽簽決定比賽名單。有人向隊(duì)員打聽比賽的名單。a說他不和x比,c說他不和x,z比,請編程序找出三隊(duì)賽手的名單2017-02-02關(guān)于Lombok @Data注解:簡化Java代碼的魔法棒
Lombok庫通過@Data注解自動生成常見的樣板代碼如getter、setter、toString等,極大減少代碼量,提高開發(fā)效率,@Data注解集成了@ToString、@EqualsAndHashCode、@Getter、@Setter、@RequiredArgsConstructor等注解的功能2024-10-10解決maven中只有Lifecycle而Dependencies和Plugins消失的問題
這篇文章主要介紹了maven中只有Lifecycle而Dependencies和Plugins消失的問題及解決方法,本文通過圖文的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-07-07SpringBoot項(xiàng)目的兩種發(fā)布方式
本文主要介紹了SpringBoot項(xiàng)目的兩種發(fā)布方式,包含jar包發(fā)布和war包發(fā)布,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07