Springboot源碼分析之Spring循環依賴揭秘
- 2019 年 10 月 3 日
- 筆記
摘要:
若你是一個有經驗的程序員,那你在開發中必然碰到過這種現象:事務不生效。或許剛說到這,有的小夥伴就會大驚失色了。Spring
不是解決了循環依賴問題嗎,它是怎麼又會發生循環依賴的呢?,接下來就讓我們一起揭秘Spring
循環依賴的最本質原因。
Spring循環依賴流程圖
Spring循環依賴發生原因
- 使用了具有代理特性的BeanPostProcessor
- 典型的有 事務註解@Transactional,異步註解@Async等
源碼分析揭秘
protected Object doCreateBean( ... ){ ... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } ... // populateBean這一句特別的關鍵,它需要給A的屬性賦值,所以此處會去實例化B~~ // 而B我們從上可以看到它就是個普通的Bean(並不需要創建代理對象),實例化完成之後,繼續給他的屬性A賦值,而此時它會去拿到A的早期引用 // 也就在此處在給B的屬性a賦值的時候,會執行到上面放進去的Bean A流程中的getEarlyBeanReference()方法 從而拿到A的早期引用~~ // 執行A的getEarlyBeanReference()方法的時候,會執行自動代理創建器,但是由於A沒有標註事務,所以最終不會創建代理,so B合格屬性引用會是A的**原始對象** // 需要注意的是:@Async的代理對象不是在getEarlyBeanReference()中創建的,是在postProcessAfterInitialization創建的代理 // 從這我們也可以看出@Async的代理它默認並不支持你去循環引用,因為它並沒有把代理對象的早期引用提供出來~~~(注意這點和自動代理創建器的區別~) // 結論:此處給A的依賴屬性字段B賦值為了B的實例(因為B不需要創建代理,所以就是原始對象) // 而此處實例B裏面依賴的A注入的仍舊為Bean A的普通實例對象(注意 是原始對象非代理對象) 註:此時exposedObject也依舊為原始對象 populateBean(beanName, mbd, instanceWrapper); // 標註有@Async的Bean的代理對象在此處會被生成~~~ 參照類:AsyncAnnotationBeanPostProcessor // 所以此句執行完成後 exposedObject就會是個代理對象而非原始對象了 exposedObject = initializeBean(beanName, exposedObject, mbd); ... // 這裡是報錯的重點~~~ if (earlySingletonExposure) { // 上面說了A被B循環依賴進去了,所以此時A是被放進了二級緩存的,所以此處earlySingletonReference 是A的原始對象的引用 // (這也就解釋了為何我說:如果A沒有被循環依賴,是不會報錯不會有問題的 因為若沒有循環依賴earlySingletonReference =null後面就直接return了) Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 上面分析了exposedObject 是被@Aysnc代理過的對象, 而bean是原始對象 所以此處不相等 走else邏輯 if (exposedObject == bean) { exposedObject = earlySingletonReference; } // allowRawInjectionDespiteWrapping 標註是否允許此Bean的原始類型被注入到其它Bean裏面,即使自己最終會被包裝(代理) // 默認是false表示不允許,如果改為true表示允許,就不會報錯啦。這是我們後面講的決方案的其中一個方案~~~ // 另外dependentBeanMap記錄著每個Bean它所依賴的Bean的Map~~~~ else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { // 我們的Bean A依賴於B,so此處值為["b"] String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); // 對所有的依賴進行一一檢查~ 比如此處B就會有問題 // 「b」它經過removeSingletonIfCreatedForTypeCheckOnly最終返返回false 因為alreadyCreated裏面已經有它了表示B已經完全創建完成了~~~ // 而b都完成了,所以屬性a也賦值完成兒聊 但是B裏面引用的a和主流程我這個A竟然不相等,那肯定就有問題(說明不是最終的)~~~ // so最終會被加入到actualDependentBeans裏面去,表示A真正的依賴~~~ 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);
肯定有值 - 緩存工廠
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
將給實例對象添加SmartInstantiationAwareBeanPostProcessor
AbstractAutoProxyCreator
是SmartInstantiationAwareBeanPostProcessor
的子類,一定記住了,一定記住,SmartInstantiationAwareBeanPostProcessor
的子類很關鍵!!!!!exposedObject = initializeBean(beanName, exposedObject, mbd);
進行BeanPostProcessor
後置處理,注意是BeanPostProcessor
!!!!!
Spring
的循環依賴被它的三級緩存給輕易解決了,但是這2個地方的後置處理帶來了 循環依賴的問題。
對比AbstractAdvisorAutoProxyCreator和AsyncAnnotationBeanPostProcessor
由於SmartInstantiationAwareBeanPostProcessor
的子類會在兩處都會執行後置處理,所以前後都會相同的對象引用,不會發生循環依賴問題,異步註解就不行了 ,至於為什麼?自己看上面的分析,仔細看哦!
如何解決循環依賴?
- 改變加載順序
@Lazy
註解allowRawInjectionDespiteWrapping
設置為true
(利用了判斷的那條語句)- 別使用相關的
BeanPostProcessor
設計到的註解,,哈哈 這不太現實。
@Lazy
@Lazy
一般含義是懶加載,它只會作用於BeanDefinition.setLazyInit()
。而此處給它增加了一個能力:延遲處理(代理處理)
// @since 4.0 出現得挺晚,它支持到了@Lazy 是功能最全的AutowireCandidateResolver public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver { // 這是此類本身唯一做的事,此處精析 // 返回該 lazy proxy 表示延遲初始化,實現過程是查看在 @Autowired 註解處是否使用了 @Lazy = true 註解 @Override @Nullable public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { // 如果isLazy=true 那就返回一個代理,否則返回null // 相當於若標註了@Lazy註解,就會返回一個代理(當然@Lazy註解的value值不能是false) return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); } // 這個比較簡單,@Lazy註解標註了就行(value屬性默認值是true) // @Lazy支持標註在屬性上和方法入參上~~~ 這裡都會解析 protected boolean isLazy(DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null) { Method method = methodParam.getMethod(); if (method == null || void.class == method.getReturnType()) { Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); if (lazy != null && lazy.value()) { return true; } } } return false; } // 核心內容,是本類的靈魂~~~ protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) { Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory, "BeanFactory needs to be a DefaultListableBeanFactory"); // 這裡毫不客氣的使用了面向實現類編程,使用了DefaultListableBeanFactory.doResolveDependency()方法~~~ final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); //TargetSource 是它實現懶加載的核心原因,在AOP那一章節了重點提到過這個接口,此處不再敘述 // 它有很多的著名實現如HotSwappableTargetSource、SingletonTargetSource、LazyInitTargetSource、 //SimpleBeanTargetSource、ThreadLocalTargetSource、PrototypeTargetSource等等非常多 // 此處因為只需要自己用,所以採用匿名內部類的方式實現~~~ 此處最重要是看getTarget方法,它在被使用的時候(也就是代理對象真正使用的時候執行~~~) TargetSource ts = new TargetSource() { @Override public Class<?> getTargetClass() { return descriptor.getDependencyType(); } @Override public boolean isStatic() { return false; } // getTarget是調用代理方法的時候會調用的,所以執行每個代理方法都會執行此方法,這也是為何doResolveDependency // 我個人認為它在效率上,是存在一定的問題的~~~所以此處建議盡量少用@Lazy~~~ //不過效率上應該還好,對比http、序列化反序列化處理,簡直不值一提 所以還是無所謂 用吧 @Override public Object getTarget() { Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null); if (target == null) { Class<?> type = getTargetClass(); // 對多值注入的空值的友好處理(不要用null) if (Map.class == type) { return Collections.emptyMap(); } else if (List.class == type) { return Collections.emptyList(); } else if (Set.class == type || Collection.class == type) { return Collections.emptySet(); } throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(), "Optional dependency not present for lazy injection point"); } return target; } @Override public void releaseTarget(Object target) { } }; // 使用ProxyFactory 給ts生成一個代理 // 由此可見最終生成的代理對象的目標對象其實是TargetSource,而TargetSource的目標才是我們業務的對象 ProxyFactory pf = new ProxyFactory(); pf.setTargetSource(ts); Class<?> dependencyType = descriptor.getDependencyType(); // 如果注入的語句是這麼寫的private AInterface a; 那這類就是借口 值是true // 把這個接口類型也得放進去(不然這個代理都不屬於這個類型,反射set的時候豈不直接報錯了嗎????) if (dependencyType.isInterface()) { pf.addInterface(dependencyType); } return pf.getProxy(beanFactory.getBeanClassLoader()); } }
標註有@Lazy
註解完成注入的時候,最終注入只是一個此處臨時生成的代理對象,只有在真正執行目標方法的時候才會去容器內拿到真是的bean
實例來執行目標方法。
利用allowRawInjectionDespiteWrapping屬性來強制改變判斷
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true); } }
這樣會導致容器裏面的是代理對象,暴露給其他實例的是原始引用,導致不生效了。由於它只對循環依賴內的Bean
受影響,所以影響範圍並不是全局,因此當找不到更好辦法的時候,此種這樣也不失是一個不錯的方案。