1. 程式人生 > 程式設計 >帶有@Transactional和@Async的迴圈依賴問題的解決

帶有@Transactional和@Async的迴圈依賴問題的解決

今天我們來探討一個有意思的spring原始碼問題,也是一個學生告訴了我現象我從原始碼裡面找到了這個有意思的問題。
首先我們看service層的程式碼案例,如下:

@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {

  @Autowired
  TransationService transationService;

  @Transactional
  @Async
  @Override
  public void transation() {
  }
}

在transation方法上面加上了@Transactional和@Async兩個註解,然後在TransationServiceImpl 類中自己把自己的例項注入到transationService屬性中,存在迴圈依賴,理論上單例的迴圈依賴是允許的。但是我們啟動容器會報錯,測試程式碼如下:

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 ‘allowEagerInit' flag turned off,for example.

從報錯的字面意思來看,是存在了多版本的迴圈依賴,如果要解決這個問題,我們必須追溯到原始碼中。

首先我們從TransationServiceImpl 例項化開始講起。
例項化從getBean方法看起,前面程式碼我就不貼了,這篇文章是給讀過spring原始碼的人看的,沒讀過也看不懂,哈哈 。

1、首先第一次建立TransationServiceImpl例項的時候會從快取中獲取例項 ,如果快取裡面有例項則直接返回,第一次建立的時候快取中是沒有例項的,所以會走到else程式碼塊中。

帶有@Transactional和@Async的迴圈依賴問題的解決

這裡是從三個快取中獲取例項化的詳細程式碼。後面會分析

	@Nullable
	protected Object getSingleton(String beanName,boolean allowEarlyReference) {
		//根據beanName從快取中拿例項
		//先從一級快取拿
		Object singletonObject = this.singletonObjects.get(beanName);
		//如果bean還正在建立,還沒建立完成,其實就是堆記憶體有了,屬性還沒有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、第一次進來快取中沒有則建立TransationServiceImpl的例項

帶有@Transactional和@Async的迴圈依賴問題的解決

最終會走到doCreateBean方法中進行例項化,部分程式碼如下

protected Object doCreateBean(final String beanName,final RootBeanDefinition mbd,final @Nullable Object[] args)
			throws BeanCreationException {

		............非關鍵程式碼不貼了

		// 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");
			}
			//這裡著重理解,對理解迴圈依賴幫助非常大,重要程度 5  新增三級快取
			addSingletonFactory(beanName,() -> getEarlyBeanReference(beanName,mbd,bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//ioc di,依賴注入的核心方法,該方法必須看,重要程度:5
			populateBean(beanName,instanceWrapper);

			//bean 例項化+ioc依賴注入完以後的呼叫,非常重要,重要程度: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.");
					}
				}
			}
		}

		............非關鍵程式碼不貼了

		return exposedObject;
	}

由於業務類有迴圈依賴

帶有@Transactional和@Async的迴圈依賴問題的解決

所以在第一次例項化業務類的時候,在populateBean(beanName,instanceWrapper);進行依賴注入時會觸發TransationServiceImpl業務類的getBean操作,也就是會呼叫TransationServiceImpl業務類的getBean方法,第二次會走到TransationServiceImpl例項化的邏輯中。這裡明白的刷朵鮮花敲個1,哈哈。

但是在觸發第二次業務類的getBean操作之前,還有一個非常重要的步驟,就是業務類的提前暴露,也就是三級快取的建立。這塊會建立業務類和ObjectFactory的對映關係這個建立對映關係是在依賴注入之前!!!!

帶有@Transactional和@Async的迴圈依賴問題的解決

3、迴圈依賴注入觸發TransationServiceImpl類的第二次getBean獲取例項化的邏輯
第二次進來的時候,由於第一次例項化的時候在三級快取中建立了對映關係,所以第二次會從快取中獲取例項

帶有@Transactional和@Async的迴圈依賴問題的解決

ObjectFactory物件的getObject方法就會呼叫到。getEarlyBeanReference方法,這個方法是會從BeanPostProcessor中獲取例項,這裡可能就會返回代理例項

帶有@Transactional和@Async的迴圈依賴問題的解決

三級快取的getObject方法會呼叫到getEarlyBeanReference中,斷點一下,看看。

帶有@Transactional和@Async的迴圈依賴問題的解決

從斷點看,
3:是獲取事務代理的BeanPostProcessor型別是SmartInstantiationAwareBeanPostProcessor型別的,所以事務代理的BeanPostProcessor會進來,然後生成代理
4:是獲取@Async非同步代理的BeanPostProcessor,但是不是SmartInstantiationAwareBeanPostProcessor型別的,所以這裡if就不會進來,所以最後這裡從三級快取中拿到的是事務切面的程式碼物件,注意這裡是類中的依賴注入的例項是事務切面的代理例項,如圖:

帶有@Transactional和@Async的迴圈依賴問題的解決

可以看到,這裡的advisors切面容器明顯是一個事務切面,所以業務類中依賴注入的是一個事務切面的代理例項。
但是在這裡我還是要說一下,在生成事務代理的時候其實是有做快取的,如下程式碼:

帶有@Transactional和@Async的迴圈依賴問題的解決

這裡的cacheKey就是TransationServiceImpl業務類的bean的名稱的字串,然後會把這個字串加入到一個earlyProxyReferences的Set容器中

在這裡已經在TransationServiceImpl的第二次getBean的時候從三級快取中獲取到了代理物件了,那麼第二次的例項化已經完成了,並且已經依賴注入到了TransationServiceImpl的屬性中了,這時候依賴注入已經完成了,好,我們還是接著第一次TransationServiceImpl的例項來講,貼程式碼:

protected Object doCreateBean(final String beanName,for example.");
					}
				}
			}
		}

		............非關鍵程式碼不貼了

		return exposedObject;
	}

也就是populateBean(beanName,instanceWrapper);依賴注入已經完成了,程式碼接著往下走。
代理會執行到:

//bean 例項化+ioc依賴注入完以後的呼叫,非常重要,重要程度:5
exposedObject = initializeBean(beanName,mbd);

在這裡,業務類會在這個方法裡面再次生成代理,這裡就有意思了。程式碼如下

	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 {
			//呼叫Aware方法
			invokeAwareMethods(beanName,bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			//對類中某些特殊方法的呼叫,比如@PostConstruct,Aware介面,非常重要 重要程度 :5
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean,beanName);
		}

		try {
			//InitializingBean介面,afterPropertiesSet,init-method屬性呼叫,非常重要,重要程度:5
			invokeInitMethods(beanName,wrappedBean,mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),"Invocation of init method failed",ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			//這個地方可能生出代理例項,是aop的入口
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);
		}

		return wrappedBean;
	}

wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);
在這個方法裡面可能會生成業務類的代理,我們看看這個方法:

@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;
	}

我們斷點看看情況

帶有@Transactional和@Async的迴圈依賴問題的解決

**效果跟我們預期的一樣,第一次例項化的時候,在屬性依賴注入的時候會在三級快取中獲取事務的代理物件,從斷點看,裡面的屬性確實是一個事務的代理物件,自己本身是沒生成代理的。

由於方法上面有 @Transactional @Async在,3,4兩個AOP入口的BeanPostProcessor中會生成相應的代理物件,這裡為什麼會生成代理物件,就不贅述了,核心思想是獲取所有advisors,然後挨個判斷advisors的pointCut是否matches這兩個註解,matches的思路是看方法上面是否有@Transactional 或@Async註解,如果有則返回true就匹配了,如果能找到匹配的切面則生成bean的代理,但是這裡要注意的是,事務切面在這裡就不會生成代理了,為什麼呢???**看程式碼

帶有@Transactional和@Async的迴圈依賴問題的解決

這裡會判斷earlyProxyReferences的Set容器中是否有這個cacheKey,這個cacheKey就是類的名稱,而這個容器在提前暴露的三級快取獲取例項的時候就已經設定進去了,所以Set容器中是有這個類的
所以3的AOP入口這裡會原樣返回Bean,如圖:

帶有@Transactional和@Async的迴圈依賴問題的解決

OK,有意思的來了,這時候就輪到4這個BeanPostProcessor的非同步切面的AOP入口執行了。如圖:

帶有@Transactional和@Async的迴圈依賴問題的解決

在這裡就返回了bean的非同步切面代理,例項如圖:

帶有@Transactional和@Async的迴圈依賴問題的解決

我解釋一下這個截圖內容,
exposedObject是非同步代理物件,在targetSource是代理物件的目標物件,目標物件中有一個transationService屬性,這個屬性是一個事務的代理物件,OK,從這裡我們發現,我去,一個同樣的類,居然生成了兩個不同的代理物件,一個是非同步的代理物件,一個是事務的代理物件,代理物件居然不一致了。為什麼會這樣,前面我已經分享得很清楚了

然後在spring中,這種情況預設是不被允許的,程式碼如下:

if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName,for example.");
					}
				}
			}
		}

Object earlySingletonReference = getSingleton(beanName,false);
這裡我們前面分析過,這裡會從三級快取中獲取到事務代理物件

if (exposedObject == bean) {
	exposedObject = earlySingletonReference;
}

然後這裡有個if判斷,bean是第一次例項化的bean,是沒被initializeBean代理之前的bean

帶有@Transactional和@Async的迴圈依賴問題的解決

而exposedObject物件是一個非同步切面的代理物件

帶有@Transactional和@Async的迴圈依賴問題的解決

這裡兩者是不相等的,而這個變數預設是allowRawInjectionDespiteWrapping=false的

帶有@Transactional和@Async的迴圈依賴問題的解決

所有這裡就會拋異常,就是文章前面的那個異常,所有我們找到了為什麼會有這麼一個異常的出現了。
其實要解決這個異常也比較簡單,只要把allowRawInjectionDespiteWrapping這個屬性變成true就行了。
如何變了,程式碼如下:

帶有@Transactional和@Async的迴圈依賴問題的解決

這是這個變數就為true了 ,就不會拋異常了

帶有@Transactional和@Async的迴圈依賴問題的解決

但是就會存在一個現象,單元測試中獲取到的bean物件和類中依賴注入的物件不是同一個了
這個bean物件是非同步代理物件

帶有@Transactional和@Async的迴圈依賴問題的解決

類中屬性的物件是事務切面的代理物件

帶有@Transactional和@Async的迴圈依賴問題的解決

有意思吧,哈哈 。

如果在類裡面沒有@Async非同步註解,其實就不會有問題,預設是允許單例迴圈依賴的,為什麼沒問題

@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,for example.");
					}
				}
			}
		}

如果只要存在迴圈依賴,第一次業務類例項化的時候代理物件就是從這裡獲取的

帶有@Transactional和@Async的迴圈依賴問題的解決

這個地方

//bean 例項化+ioc依賴注入完以後的呼叫,非常重要,重要程度:5
			exposedObject = initializeBean(beanName,mbd);

由於三級快取中建立了快取了

帶有@Transactional和@Async的迴圈依賴問題的解決

所以會直接返回對應的bean,沒有生成代理。代理物件是從這個獲取的

帶有@Transactional和@Async的迴圈依賴問題的解決

是從提前暴露的三級快取中獲取的代理物件賦值給了第一次例項化的bean物件,所以這個else if中可能出現異常的地方就不會走了,因為這兩個bean exposedObject 和 bean是相等的。

到此這篇關於帶有@Transactional和@Async的迴圈依賴問題的解決的文章就介紹到這了,更多相關@Transactional和@Async的迴圈依賴內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!