帶有@Transactional和@Async的循環(huán)依賴問(wèn)題的解決
今天我們來(lái)探討一個(gè)有意思的spring源碼問(wèn)題,也是一個(gè)學(xué)生告訴了我現(xiàn)象我從源碼里面找到了這個(gè)有意思的問(wèn)題。
首先我們看service層的代碼案例,如下:
@Service("transationServiceImpl") public class TransationServiceImpl implements TransationService { @Autowired TransationService transationService; @Transactional @Async @Override public void transation() { } }
在transation方法上面加上了@Transactional和@Async兩個(gè)注解,然后在TransationServiceImpl 類中自己把自己的實(shí)例注入到transationService屬性中,存在循環(huán)依賴,理論上單例的循環(huán)依賴是允許的。但是我們啟動(dòng)容器會(huì)報(bào)錯(cuò),測(cè)試代碼如下:
public class MyTest { @Test public void test1() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanBean.class); } } @Component @ComponentScan(basePackages = {"com.xiangxue"}) public class ComponentScanBean { }
然后右鍵運(yùn)行test1單元測(cè)試加載spring容器就會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息如下:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘transationServiceImpl': Bean with name ‘transationServiceImpl' has been injected into other beans [transationServiceImpl] 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 ‘a(chǎn)llowEagerInit' flag turned off, for example.
從報(bào)錯(cuò)的字面意思來(lái)看,是存在了多版本的循環(huán)依賴,如果要解決這個(gè)問(wèn)題,我們必須追溯到源碼中。
首先我們從TransationServiceImpl 實(shí)例化開(kāi)始講起。
實(shí)例化從getBean方法看起,前面代碼我就不貼了,這篇文章是給讀過(guò)spring源碼的人看的,沒(méi)讀過(guò)也看不懂,哈哈 。
1、首先第一次創(chuàng)建TransationServiceImpl實(shí)例的時(shí)候會(huì)從緩存中獲取實(shí)例 ,如果緩存里面有實(shí)例則直接返回,第一次創(chuàng)建的時(shí)候緩存中是沒(méi)有實(shí)例的,所以會(huì)走到else代碼塊中。
這里是從三個(gè)緩存中獲取實(shí)例化的詳細(xì)代碼。后面會(huì)分析
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { //根據(jù)beanName從緩存中拿實(shí)例 //先從一級(jí)緩存拿 Object singletonObject = this.singletonObjects.get(beanName); //如果bean還正在創(chuàng)建,還沒(méi)創(chuàng)建完成,其實(shí)就是堆內(nèi)存有了,屬性還沒(méi)有DI依賴注入 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //從二級(jí)緩存中拿 singletonObject = this.earlySingletonObjects.get(beanName); //如果還拿不到,并且允許bean提前暴露 if (singletonObject == null && allowEarlyReference) { //從三級(jí)緩存中拿到對(duì)象工廠 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //從工廠中拿到對(duì)象 singletonObject = singletonFactory.getObject(); //升級(jí)到二級(jí)緩存 System.out.println("======get instance from 3 level cache->beanName->" + beanName + "->value->" + singletonObject ); this.earlySingletonObjects.put(beanName, singletonObject); //刪除三級(jí)緩存 this.singletonFactories.remove(beanName); } } } } return singletonObject; }
2、第一次進(jìn)來(lái)緩存中沒(méi)有則創(chuàng)建TransationServiceImpl的實(shí)例
最終會(huì)走到doCreateBean方法中進(jìn)行實(shí)例化,部分代碼如下
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { ............非關(guān)鍵代碼不貼了 // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. //是否 單例bean提前暴露 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } //這里著重理解,對(duì)理解循環(huán)依賴幫助非常大,重要程度 5 添加三級(jí)緩存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { //ioc di,依賴注入的核心方法,該方法必須看,重要程度:5 populateBean(beanName, mbd, instanceWrapper); //bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } 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); for (String dependentBean : dependentBeans) { 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."); } } } } ............非關(guān)鍵代碼不貼了 return exposedObject; }
由于業(yè)務(wù)類有循環(huán)依賴
所以在第一次實(shí)例化業(yè)務(wù)類的時(shí)候,在populateBean(beanName, mbd, instanceWrapper);進(jìn)行依賴注入時(shí)會(huì)觸發(fā)TransationServiceImpl業(yè)務(wù)類的getBean操作,也就是會(huì)調(diào)用TransationServiceImpl業(yè)務(wù)類的getBean方法,第二次會(huì)走到TransationServiceImpl實(shí)例化的邏輯中。這里明白的刷朵鮮花敲個(gè)1,哈哈。
但是在觸發(fā)第二次業(yè)務(wù)類的getBean操作之前,還有一個(gè)非常重要的步驟,就是業(yè)務(wù)類的提前暴露,也就是三級(jí)緩存的建立。這塊會(huì)建立業(yè)務(wù)類和ObjectFactory的映射關(guān)系這個(gè)建立映射關(guān)系是在依賴注入之前?。。?!
3、循環(huán)依賴注入觸發(fā)TransationServiceImpl類的第二次getBean獲取實(shí)例化的邏輯
第二次進(jìn)來(lái)的時(shí)候,由于第一次實(shí)例化的時(shí)候在三級(jí)緩存中建立了映射關(guān)系,所以第二次會(huì)從緩存中獲取實(shí)例
ObjectFactory對(duì)象的getObject方法就會(huì)調(diào)用到。getEarlyBeanReference方法,這個(gè)方法是會(huì)從BeanPostProcessor中獲取實(shí)例,這里可能就會(huì)返回代理實(shí)例
三級(jí)緩存的getObject方法會(huì)調(diào)用到getEarlyBeanReference中,斷點(diǎn)一下,看看。
從斷點(diǎn)看,
3:是獲取事務(wù)代理的BeanPostProcessor類型是SmartInstantiationAwareBeanPostProcessor類型的,所以事務(wù)代理的BeanPostProcessor會(huì)進(jìn)來(lái),然后生成代理
4:是獲取@Async異步代理的BeanPostProcessor,但是不是SmartInstantiationAwareBeanPostProcessor類型的,所以這里if就不會(huì)進(jìn)來(lái),所以最后這里從三級(jí)緩存中拿到的是事務(wù)切面的代碼對(duì)象,注意這里是類中的依賴注入的實(shí)例是事務(wù)切面的代理實(shí)例,如圖:
可以看到,這里的advisors切面容器明顯是一個(gè)事務(wù)切面,所以業(yè)務(wù)類中依賴注入的是一個(gè)事務(wù)切面的代理實(shí)例。
但是在這里我還是要說(shuō)一下,在生成事務(wù)代理的時(shí)候其實(shí)是有做緩存的,如下代碼:
這里的cacheKey就是TransationServiceImpl業(yè)務(wù)類的bean的名稱的字符串,然后會(huì)把這個(gè)字符串加入到一個(gè)earlyProxyReferences的Set容器中
在這里已經(jīng)在TransationServiceImpl的第二次getBean的時(shí)候從三級(jí)緩存中獲取到了代理對(duì)象了,那么第二次的實(shí)例化已經(jīng)完成了,并且已經(jīng)依賴注入到了TransationServiceImpl的屬性中了,這時(shí)候依賴注入已經(jīng)完成了,好,我們還是接著第一次TransationServiceImpl的實(shí)例來(lái)講,貼代碼:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { ............非關(guān)鍵代碼不貼了 // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. //是否 單例bean提前暴露 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } //這里著重理解,對(duì)理解循環(huán)依賴幫助非常大,重要程度 5 添加三級(jí)緩存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { //ioc di,依賴注入的核心方法,該方法必須看,重要程度:5 populateBean(beanName, mbd, instanceWrapper); //bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } 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); for (String dependentBean : dependentBeans) { 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."); } } } } ............非關(guān)鍵代碼不貼了 return exposedObject; }
也就是populateBean(beanName, mbd, instanceWrapper);依賴注入已經(jīng)完成了,代碼接著往下走。
代理會(huì)執(zhí)行到:
//bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5 exposedObject = initializeBean(beanName, exposedObject, mbd);
在這里,業(yè)務(wù)類會(huì)在這個(gè)方法里面再次生成代理,這里就有意思了。代碼如下
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { //調(diào)用Aware方法 invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { //對(duì)類中某些特殊方法的調(diào)用,比如@PostConstruct,Aware接口,非常重要 重要程度 :5 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { //InitializingBean接口,afterPropertiesSet,init-method屬性調(diào)用,非常重要,重要程度:5 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { //這個(gè)地方可能生出代理實(shí)例,是aop的入口 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
在這個(gè)方法里面可能會(huì)生成業(yè)務(wù)類的代理,我們看看這個(gè)方法:
@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; }
我們斷點(diǎn)看看情況
**效果跟我們預(yù)期的一樣,第一次實(shí)例化的時(shí)候,在屬性依賴注入的時(shí)候會(huì)在三級(jí)緩存中獲取事務(wù)的代理對(duì)象,從斷點(diǎn)看,里面的屬性確實(shí)是一個(gè)事務(wù)的代理對(duì)象,自己本身是沒(méi)生成代理的。
由于方法上面有 @Transactional @Async在,3,4兩個(gè)AOP入口的BeanPostProcessor中會(huì)生成相應(yīng)的代理對(duì)象,這里為什么會(huì)生成代理對(duì)象,就不贅述了,核心思想是獲取所有advisors,然后挨個(gè)判斷advisors的pointCut是否matches這兩個(gè)注解,matches的思路是看方法上面是否有@Transactional 或@Async注解,如果有則返回true就匹配了,如果能找到匹配的切面則生成bean的代理,但是這里要注意的是,事務(wù)切面在這里就不會(huì)生成代理了,為什么呢???**看代碼
這里會(huì)判斷earlyProxyReferences的Set容器中是否有這個(gè)cacheKey,這個(gè)cacheKey就是類的名稱,而這個(gè)容器在提前暴露的三級(jí)緩存獲取實(shí)例的時(shí)候就已經(jīng)設(shè)置進(jìn)去了,所以Set容器中是有這個(gè)類的
所以3的AOP入口這里會(huì)原樣返回Bean,如圖:
OK,有意思的來(lái)了,這時(shí)候就輪到4這個(gè)BeanPostProcessor的異步切面的AOP入口執(zhí)行了。如圖:
在這里就返回了bean的異步切面代理,實(shí)例如圖:
我解釋一下這個(gè)截圖內(nèi)容,
exposedObject是異步代理對(duì)象,在targetSource是代理對(duì)象的目標(biāo)對(duì)象,目標(biāo)對(duì)象中有一個(gè)transationService屬性,這個(gè)屬性是一個(gè)事務(wù)的代理對(duì)象,OK,從這里我們發(fā)現(xiàn),我去,一個(gè)同樣的類,居然生成了兩個(gè)不同的代理對(duì)象,一個(gè)是異步的代理對(duì)象,一個(gè)是事務(wù)的代理對(duì)象,代理對(duì)象居然不一致了。為什么會(huì)這樣,前面我已經(jīng)分享得很清楚了
然后在spring中,這種情況默認(rèn)是不被允許的,代碼如下:
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); for (String dependentBean : dependentBeans) { 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."); } } } }
Object earlySingletonReference = getSingleton(beanName, false);
這里我們前面分析過(guò),這里會(huì)從三級(jí)緩存中獲取到事務(wù)代理對(duì)象
if (exposedObject == bean) { exposedObject = earlySingletonReference; }
然后這里有個(gè)if判斷,bean是第一次實(shí)例化的bean,是沒(méi)被initializeBean代理之前的bean
而exposedObject對(duì)象是一個(gè)異步切面的代理對(duì)象
這里兩者是不相等的,而這個(gè)變量默認(rèn)是allowRawInjectionDespiteWrapping=false的
所有這里就會(huì)拋異常,就是文章前面的那個(gè)異常,所有我們找到了為什么會(huì)有這么一個(gè)異常的出現(xiàn)了。
其實(shí)要解決這個(gè)異常也比較簡(jiǎn)單,只要把a(bǔ)llowRawInjectionDespiteWrapping這個(gè)屬性變成true就行了。
如何變了,代碼如下:
這是這個(gè)變量就為true了 ,就不會(huì)拋異常了
但是就會(huì)存在一個(gè)現(xiàn)象,單元測(cè)試中獲取到的bean對(duì)象和類中依賴注入的對(duì)象不是同一個(gè)了
這個(gè)bean對(duì)象是異步代理對(duì)象
類中屬性的對(duì)象是事務(wù)切面的代理對(duì)象
有意思吧,哈哈 。
如果在類里面沒(méi)有@Async異步注解,其實(shí)就不會(huì)有問(wèn)題,默認(rèn)是允許單例循環(huán)依賴的,為什么沒(méi)問(wèn)題
@Service("transationServiceImpl") public class TransationServiceImpl implements TransationService { @Autowired TransationService transationService; @Transactional @Override public void transation() { System.out.println(transationService.hashCode()); System.out.println("s"); } }
因?yàn)?/p>
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); for (String dependentBean : dependentBeans) { 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."); } } } }
如果只要存在循環(huán)依賴,第一次業(yè)務(wù)類實(shí)例化的時(shí)候代理對(duì)象就是從這里獲取的
這個(gè)地方
//bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5 exposedObject = initializeBean(beanName, exposedObject, mbd);
由于三級(jí)緩存中建立了緩存了
所以會(huì)直接返回對(duì)應(yīng)的bean,沒(méi)有生成代理。代理對(duì)象是從這個(gè)獲取的
是從提前暴露的三級(jí)緩存中獲取的代理對(duì)象賦值給了第一次實(shí)例化的bean對(duì)象,所以這個(gè)else if中可能出現(xiàn)異常的地方就不會(huì)走了,因?yàn)檫@兩個(gè)bean exposedObject 和 bean是相等的。
到此這篇關(guān)于帶有@Transactional和@Async的循環(huán)依賴問(wèn)題的解決的文章就介紹到這了,更多相關(guān)@Transactional和@Async的循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot RestTemplate GET POST請(qǐng)求的實(shí)例講解
這篇文章主要介紹了SpringBoot RestTemplate GET POST請(qǐng)求的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09

java正則替換括號(hào)中的逗號(hào)實(shí)現(xiàn)示例

Spring Security使用單點(diǎn)登錄的權(quán)限功能

Java技能點(diǎn)之SimpleDateFormat進(jìn)行日期格式化問(wèn)題

Spring?Boot之Validation自定義實(shí)現(xiàn)方式的總結(jié)