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

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

Java技能點之SimpleDateFormat進行日期格式化問題

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