摘要:從創建的的起點開始首先會在緩存中查找,這里一共有三級緩存,保存初始化完成的單例實例,保存提前曝光的單例實例,保存單例的工廠函數對象后面兩級都是為了解決循環依賴設置的,具體查找邏輯在后續其他情況下調用會說明。
源起
在開發過程中,遇到需要把方法調用改為異步的情況,本來以為簡單得加個@Asyn在方法上就行了,沒想到項目啟動的時候報了如下的錯誤:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name "customerServiceImpl": Bean with name "customerServiceImpl" has been injected into other beans [customerServiceImpl,followServiceImpl,cupidService] 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.
看了下好像報的是循環依賴的錯誤,但是Spring單例是支持循環依賴的,當時一臉懵逼。
拿著報錯去百度了下,說是多個動態代理導致的循環依賴報錯,也找到了報錯的地點,但是還是不明白為什么會這樣,所以打算深入源碼探個究竟,順便回顧下Bean的獲取流程和循環依賴的內容。
用SpringBoot新建一個demo項目,因為原項目是有定義切面的,這里也定義一個切面:
@Aspect @Component public class TestAspect { @Pointcut("execution(public * com.example.demo.service.CyclicDependencyService.sameClassMethod(..))") private void testPointcut() {} @AfterReturning("testPointcut()") public void after(JoinPoint point) { System.out.println("在" + point.getSignature() + "之后干點事情"); } }
然后新建一個注入自己的Service構成循環依賴,然后提供一個方法滿足切點要求,并且加上@Async注解:
@Service public class CyclicDependencyService { @Autowired private CyclicDependencyService cyclicDependencyService; public void test() { System.out.println("調用同類方法"); cyclicDependencyService.sameClassMethod(); } @Async public void sameClassMethod() { System.out.println("循環依賴中的異步方法"); System.out.println("方法線程:" + Thread.currentThread().getName()); } }
還有別忘了給Application啟動類加上@EnableAsync和@EnableAspectJAutoProxy:
@EnableAsync @EnableAspectJAutoProxy @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
最后打好斷點,開始debug。
debug從Bean創建的的起點--AbstractBeanFactory#getBean開始
// Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName);
首先會在緩存中查找,DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference):
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
這里一共有三級緩存:
singletonObjects,保存初始化完成的單例bean實例;
earlySingletonObjects,保存提前曝光的單例bean實例;
singletonFactories,保存單例bean的工廠函數對象;
后面兩級都是為了解決循環依賴設置的,具體查找邏輯在后續其他情況下調用會說明。
緩存中找不到,就要創建單例:
sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } });
調用DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory> singletonFactory):
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) { ... beforeSingletonCreation(beanName); ... singletonObject = singletonFactory.getObject(); ... afterSingletonCreation(beanName); ... addSingleton(beanName, singletonObject); ... }
創建前后分別做了這幾件事:
前,beanName放入singletonsCurrentlyInCreation,表示單例正在創建中
后,從singletonsCurrentlyInCreation中移除beanName
后,將創建好的bean放入singletonObjects,移除在singletonFactories和earlySingletonObjects的對象
創建單例調用getSingleton時傳入的工廠函數對象的getObject方法,實際上就是createBean方法,主要邏輯在AbstractAutowireCapableBeanFactory#doCreateBean中:
... instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); ... // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. 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"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } ... 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); SetactualDependentBeans = 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."); } } } }
可以看到報錯就是在這個方法里拋出的,那么這個方法就是重點中的重點。
首先實例化單例,instantiate,只是實例化獲取對象引用,還沒有注入依賴。我debug時記錄的bean對象是CyclicDependencyService@4509;
然后判斷bean是否需要提前暴露,需要滿足三個條件:1、是單例;2、支持循環依賴;3、bean正在創建中,也就是到前面提到的singletonsCurrentlyInCreation中能查找到,全滿足的話就會調用DefaultSingletonBeanRegistry#addSingletonFactory把beanName和單例工廠函數對象(匿名實現調用AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法)放入singletonFactories;
接著就是注入依賴,填充屬性,具體怎么注入這里就不展開了,最后會為屬性cyclicDependencyService調用DefaultSingletonBeanRegistry.getSingleton(beanName, true),注意這里和最開始的那次調用不一樣,isSingletonCurrentlyInCreation為true,就會在singletonFactories中找到bean的單例工廠函數對象,也就是在上一步提前暴露時放入的,然后調用它的匿名實現AbstractAutowireCapableBeanFactory#getEarlyBeanReference:
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; }
方法邏輯就是挨個調用實現了SmartInstantiationAwareBeanPostProcessor接口的后置處理器(以下簡稱BBP)的getEarlyBeanReference方法。一個一個debug下來,其他都是原樣返回bean,只有AnnotationAwareAspectJAutoProxyCreator會把原bean(CyclicDependencyService@4509)存在earlyProxyReferences,然后將bean的代理返回(debug時記錄的返回對象是CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740)并放入earlySingletonObjects,再賦給屬性cyclicDependencyService。
public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }
屬性填充完成后就是調用初始化方法AbstractAutowireCapableBeanFactory#initializeBean:
... invokeAwareMethods(beanName, bean); ... wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); ... invokeInitMethods(beanName, wrappedBean, mbd); ... wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); ...
初始化主要分為這幾步:
如果bean實現了BeanNameAware、BeanClassLoaderAware或BeanFactoryAware,把相應的資源放入bean;
順序執行BBP的postProcessBeforeInitialization方法;
如果實現了InitializingBean就執行afterPropertiesSet方法,然后執行自己的init-method;
順序執行BBP的postProcessAfterInitialization。
debug的時候發現是第4步改變了bean,先執行AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
這里會獲取并移除之前存在earlyProxyReferences的bean(CyclicDependencyService@4509),因為和當前bean是同一個對象,所以什么都沒做直接返回。隨后會執行AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization:
if (isEligible(bean, beanName)) { ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName); if (!proxyFactory.isProxyTargetClass()) { evaluateProxyInterfaces(bean.getClass(), proxyFactory); } proxyFactory.addAdvisor(this.advisor); customizeProxyFactory(proxyFactory); return proxyFactory.getProxy(getProxyClassLoader()); }
先判斷bean是否有需要代理,因為CyclicDependencyService有方法帶有@Async注解就需要代理,返回代理對象是CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273。
返回的代理對象賦值給AbstractAutowireCapableBeanFactory#doCreateBean方法內的exposedObject,接下來就到了檢查循環依賴的地方了:
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); SetactualDependentBeans = 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."); } } } }
首先從earlySingletonObjects里拿到前面屬性填充時放入的bean代理(CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740),不為空的話就比較bean和exposedObject,分別是CyclicDependencyService@4509和CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273,很明顯不是同一個對象,然后會判斷allowRawInjectionDespiteWrapping屬性和是否有依賴的bean,然后判斷這些bean是否是真實依賴的,一旦存在真實依賴的bean,就會拋出BeanCurrentlyInCreationException。
總結總結下Spring解決循環依賴的思路:在創建bean時,對于滿足提前曝光條件的單例,會把該單例的工廠函數對象放入三級緩存中的singletonFactories中;然后在填充屬性時,如果存在循環依賴,必然會嘗試獲取該單例,也就是執行之前放入的工廠函數的匿名實現,這時候拿到的有可能是原bean對象,也有可能是被某些BBP處理過返回的代理對象,會放入三級緩存中的earlySingletonObjects中;接著bean開始初始化,結果返回的有可能是原bean對象,也有可能是代理對象;最后對于滿足提前曝光的單例,如果真的有提前曝光的動作,就會去檢查初始化后的bean對象是不是原bean對象是同一個對象,只有不是的情況下才可能拋出異常。重點就在于存在循環依賴的情況下,初始化過的bean對象是不是跟原bean是同一個對象。
從以上的debug過程可以看出,是AsyncAnnotationBeanPostProcessor這個BBP在初始化過程中改變了bean,使得結果bean和原bean不是一個對象,而AnnotationAwareAspectJAutoProxyCreator則是在填充屬性獲取提前曝光的對象時把原始bean緩存起來,返回代理的bean。然后在初始化時執行它的postProcessAfterInitialization方法時如果傳入的bean是之前緩存的原始bean,就直接返回,不進行代理。如果其他BBP也都沒有改變bean的話,初始化過后的bean就是跟原始bean是同一個對象,這時就會把提前曝光的對象(代理過的)作為最終生成的bean。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77558.html
摘要:實例化時,發現又依賴于。一些緩存的介紹在進行源碼分析前,我們先來看一組緩存的定義。可是看完源碼后,我們似乎仍然不知道這些源碼是如何解決循環依賴問題的。 1. 簡介 本文,我們來看一下 Spring 是如何解決循環依賴問題的。在本篇文章中,我會首先向大家介紹一下什么是循環依賴。然后,進入源碼分析階段。為了更好的說明 Spring 解決循環依賴的辦法,我將會從獲取 bean 的方法getB...
摘要:實戰高并發程序設計這本書是目前點評推薦比較多的書,其特色是案例小,好實踐代碼有場景,實用。想要學習多線程的朋友,這本書是我大力推薦的,我的個人博客里面二十多篇的多線程博文都是基于此書,并且在這本書的基礎上進行提煉和總結而寫出來的。 學習的最好途徑就是看書,這是我自己學習并且小有了一定的積累之后的第一體會。個人認為看書有兩點好處:showImg(/img/bVr5S5); 1.能出版出...
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...
摘要:框架具有輕便,開源的優點,所以本譯見構建用戶管理微服務五使用令牌和來實現身份驗證往期譯見系列文章在賬號分享中持續連載,敬請查看在往期譯見系列的文章中,我們已經建立了業務邏輯數據訪問層和前端控制器但是忽略了對身份進行驗證。 重拾后端之Spring Boot(四):使用JWT和Spring Security保護REST API 重拾后端之Spring Boot(一):REST API的搭建...
閱讀 2787·2021-11-17 09:33
閱讀 2169·2021-09-03 10:40
閱讀 522·2019-08-29 18:45
閱讀 2956·2019-08-29 16:21
閱讀 613·2019-08-29 11:11
閱讀 3394·2019-08-26 12:00
閱讀 2947·2019-08-23 18:19
閱讀 1094·2019-08-23 12:18