Springboot源碼分析之Spring循環依賴揭祕

摘要:

若你是一個有經驗的程序員,那你在開發中必然碰到過這種現象:事務不生效。或許剛說到這,有的小夥伴就會大驚失色了。Spring不是解決了循環依賴問題嗎,它是怎麼又會發生循環依賴的呢?,接下來就讓咱們一塊兒揭祕Spring循環依賴的最本質緣由。java

Spring循環依賴流程圖

file

Spring循環依賴發生緣由

  • 使用了具備代理特性的BeanPostProcessor
  • 典型的有 事務註解@Transactional,異步註解@Async等 file

file

file

源碼分析揭祕

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
  • AbstractAutoProxyCreatorSmartInstantiationAwareBeanPostProcessor的子類,必定記住了,必定記住,SmartInstantiationAwareBeanPostProcessor的子類很關鍵!!!!!
  • exposedObject = initializeBean(beanName, exposedObject, mbd);進行BeanPostProcessor後置處理,注意是BeanPostProcessor!!!!!

Spring的循環依賴被它的三級緩存給輕易解決了,可是這2個地方的後置處理帶來了 循環依賴的問題。程序員

對比AbstractAdvisorAutoProxyCreator和AsyncAnnotationBeanPostProcessor編程

file

file

因爲SmartInstantiationAwareBeanPostProcessor的子類會在兩處都會執行後置處理,因此先後都會相同的對象引用,不會發生循環依賴問題,異步註解就不行了 ,至於爲何?本身看上面的分析,仔細看哦!緩存

如何解決循環依賴?

  • 改變加載順序
  • @Lazy註解
  • allowRawInjectionDespiteWrapping設置爲true(利用了判斷的那條語句)
  • 別使用相關的BeanPostProcessor設計到的註解,,哈哈 這不太現實。 file

@Lazy

@Lazy通常含義是懶加載,它只會做用於BeanDefinition.setLazyInit()。而此處給它增長了一個能力:延遲處理(代理處理)app

// @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屬性來強制改變判斷ide

@Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
        }
    }

這樣會致使容器裏面的是代理對象,暴露給其餘實例的是原始引用,致使不生效了。因爲它只對循環依賴內的Bean受影響,因此影響範圍並非全局,所以當找不到更好辦法的時候,此種這樣也不失是一個不錯的方案。源碼分析

相關文章
相關標籤/搜索