Spring容器如何解決迴圈依賴的原理
Spring容器如何解決迴圈依賴的原理
最近看原始碼在研究類似如下配置的迴圈依賴是怎麼解決的?
1 <bean id="a" class="com.project.demo.A" scope="singleton"> 2 <property name="b" ref="b"/> 3 </bean> 4 <bean id="b" class="com.project.demo.B" scope="singleton"> 5 <property name="a" ref="a"/> 6 </bean>
說明:
1、Spring容器解決迴圈依賴的問題配置類必須是單例模式scope="singleton"才支援,如果是scope="prototype"是無法解決迴圈依賴的。
2、Spring容器解決迴圈依賴主要依靠三級快取機制
2.1 一級快取使用的map: private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
2.2 二級快取使用的map: private final Map<String, Object> earlySingletonObjects = new HashMap(16);
2.3 三級快取使用的map:private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
3、Spring容器解決迴圈依賴簡潔概述主要有四大流程方法:獲取物件 getSingleton()、建立物件(例項化) doCreateBean()、填充屬性(初始化) populateBean()、返回物件 addSingleton()
在系統啟動獲取配置檔案後,程式是依次讀取並載入的,所以上面配置檔案程式碼,先例項化a物件,然後初始化a物件給a新增b屬性,再例項化b物件,最後初始化b物件給新增屬性a.
那麼在程式碼執行過程中,先呼叫getSingleton()方法,我們檢視原始碼
1 @Nullable 2 public Object getSingleton(String beanName) {
// 呼叫下方過載方法 3 return this.getSingleton(beanName, true); 4 } 5 6 @Nullable 7 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先從一級快取中獲取a物件的例項 8 Object singletonObject = this.singletonObjects.get(beanName);
// 如果從一級快取中獲取不到a物件,那麼檢查該物件是否正在被建立,如果正在被建立,則進入if迴圈中 9 if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { 10 synchronized(this.singletonObjects) {
// 從二級快取中獲取該物件 11 singletonObject = this.earlySingletonObjects.get(beanName);
// 如果二級快取中無法獲取該物件,那麼一定會進入如下if方法,因為allowEarlyReference傳過來的時候就是true 12 if (singletonObject == null && allowEarlyReference) {
// 從三級快取中獲取該物件 13 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); 14 if (singletonFactory != null) {
// 如果獲取到了該物件,就將三級快取中的物件放到二級快取中,並且將三級快取中的物件刪除 15 singletonObject = singletonFactory.getObject(); 16 this.earlySingletonObjects.put(beanName, singletonObject); 17 this.singletonFactories.remove(beanName); 18 } 19 } 20 } 21 } 22 23 return singletonObject; 24 }
從三面的原始碼發現,如果a第一次獲取,那麼第9行的if語句為false,將直接放回為null,這時回到建立物件doCreateBean()方法,該方法使用反射的方式生成a物件,並且該物件在三級快取中,物件生成後就需要對a物件進行屬性填充:
1 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { 2 // 省略多行程式碼,大致就是呼叫各種方法,通過反射建立物件 3 try { 4 // a物件建立完成,呼叫屬性填充方法,對a進行屬性填充 5 this.populateBean(beanName, mbd, instanceWrapper); 6 exposedObject = this.initializeBean(beanName, exposedObject, mbd); 7 } catch (Throwable var18) { 8 if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) { 9 throw (BeanCreationException)var18; 10 } 11 12 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18); 13 } 14 15 if (earlySingletonExposure) { 16 Object earlySingletonReference = this.getSingleton(beanName, false); 17 if (earlySingletonReference != null) { 18 // 省略多行程式碼 19 } 20 } 22 // 省略多行程式碼 23 }
在上面程式碼doCreateBean()方法中先建立a物件,建立完成後會呼叫this.populateBean(beanName, mbd, instanceWrapper)方法對a進行屬性填出,這個時候會獲取配置檔案中所有<bean id="a">裡面的所有屬性,發現會存在一個b屬性,下面貼出部分原始碼
1 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { 2 if (bw == null) { 3 if (mbd.hasPropertyValues()) { 4 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); 5 } 6 } else { 7 boolean continueWithPropertyPopulation = true; 8 if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) { 9 // 刪除大量程式碼 10 } 11 12 if (continueWithPropertyPopulation) { 13 // 刪除大量原始碼,applyPropertyValues方法中beanName為a,pvs為狀態各種屬性的PropertyValues物件,pvs就裝有b這個屬性 14 if (pvs != null) { 15 this.applyPropertyValues(beanName, mbd, bw, (PropertyValues)pvs); 16 } 17 18 } 19 } 20 }
繼續跟進applyPropertyValues方法的原始碼
1 protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { 2 if (!pvs.isEmpty()) { 3 if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) { 4 ((BeanWrapperImpl)bw).setSecurityContext(this.getAccessControlContext()); 5 } 6 MutablePropertyValues mpvs = null; 7 List original; 8 if (pvs instanceof MutablePropertyValues) { 9 // 省略大量程式碼 10 } else { 11 original = Arrays.asList(pvs.getPropertyValues()); 12 } 13 // 省略大量程式碼 大致過程是將屬性物件pvs 轉化成original List物件,然後在使用迭代器在下面進行迭代 14 Iterator var11 = original.iterator(); 15 while(true) { 16 while(var11.hasNext()) { 17 PropertyValue pv = (PropertyValue)var11.next(); 18 if (pv.isConverted()) { 19 deepCopy.add(pv); 20 } else { 21 String propertyName = pv.getName(); 22 Object originalValue = pv.getValue(); 23 // 通過下面方法解決依賴的b,整個方法在迭代器中,外層在while(true)中,可能有多個屬性,迴圈直到所有屬性都解決了就return;或者丟擲異常 24 Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); 25 // 省略大量程式碼 26 } 27 } 28 29 if (mpvs != null && !resolveNecessary) { 30 mpvs.setConverted(); 31 } 32 33 try { 34 bw.setPropertyValues(new MutablePropertyValues(deepCopy)); 35 return; 36 } catch (BeansException var19) { 37 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", var19); 38 } 39 } 40 } 41 }
繼續跟進上面紅色方法
1 public Object resolveValueIfNecessary(Object argName, @Nullable Object value) { 2 if (value instanceof RuntimeBeanReference) { 3 RuntimeBeanReference ref = (RuntimeBeanReference)value; 4 return this.resolveReference(argName, ref); 5 } else if (value instanceof RuntimeBeanNameReference) { 6 // 省略多行程式碼 7 } 8 // 省略多行程式碼 9 }
繼續跟進紅色部分的程式碼
1 private Object resolveReference(Object argName, RuntimeBeanReference ref) { 2 try { 3 String refName = ref.getBeanName(); 4 refName = String.valueOf(this.doEvaluate(refName)); 5 Object bean; 6 if (ref.isToParent()) { 7 // 省略多行程式碼 8 } else { 9 // 通過refName的值b又去工廠找b物件 10 bean = this.beanFactory.getBean(refName); 11 this.beanFactory.registerDependentBean(refName, this.beanName); 12 } 13 14 // 省略多行程式碼 15 return bean; 16 } catch (BeansException var5) { 17 throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName, "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, var5); 18 } 19 }
1 public Object getBean(String name) throws BeansException { 2 return this.doGetBean(name, (Class)null, (Object[])null, false); 3 }
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = this.transformedBeanName(name); // 跟了這麼就,最終表明在例項化a物件後,在裝載a的屬性b時,會經過各種校驗最終到getSingleton(),及先獲取b物件,如果b物件不存在則會對b完成建立的過程 Object sharedInstance = this.getSingleton(beanName); // 省略多行程式碼 }
上面整個過程概括:在檢測到需要例項化a時,先去獲取a物件,看a是否已經存在,獲取去先從一級快取中獲取,如果沒有並且如果a也沒有正在例項化,那麼直接返回null,表明獲取不到a物件,那麼此時呼叫doCreateBean()方法完成對a物件的例項化過程(通過反射建立a物件),並且將建立的a物件放在三級快取中,然後繼續執行doCreateBean中的populateBean()方法完成對a進行初始化即新增屬性b,經過一些列校驗,最終又會呼叫getSingleton()方法來獲取b物件,同樣會返回null,這個時候就會去執行doCreateBean()方法建立b物件,同樣過反射建立b,當b物件建立完成時也會存放在三級快取中,在例項化b物件完成,然後繼續執行doCreateBean中的populateBean()方法,也需要初始化b物件,填充b的屬性,這時發現b物件的屬性是a,同樣再次通過getSingleton()方法獲取a,獲取a的過程如第一個原始碼部分,先從一級快取中獲取,獲取不到,然後判斷a正在建立中,然後就從二級、三級快取中獲取,最終在三級快取中獲取到了a,並且將三級快取中的a物件放到二級快取中,並將刪除三級快取中的a,此時b物件初始化也完成。
在a物件初始化的流程中,將b物件也例項化和初始化了,在b的初始化過程中,將a從三級快取移到了二級快取中,當b初始化完成後繼續向下執行,會執行到addSingleton(),檢視原始碼
protected void addSingleton(String beanName, Object singletonObject) { synchronized(this.singletonObjects) {
// 將b加入到1級快取 this.singletonObjects.put(beanName, singletonObject);
// 將b從三級快取中刪除 this.singletonFactories.remove(beanName);
// 將b從二級快取中刪除(b在二級快取中沒有,即空刪除) this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
從上面程式碼可以看出在,並建立完成並且初始化後,會將B從三級快取中直接放到一級快取中,並且刪除三級快取中的資料。
所有b工作做完後返回到a初始化屬性b的程式碼
1 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { 2 3 // 省略大量程式碼 4 try { 5 // 完成a對於屬性b的檢查裝配工作後返回到方法populateBean(),繼續向下執行 6 this.populateBean(beanName, mbd, instanceWrapper); 7 exposedObject = this.initializeBean(beanName, exposedObject, mbd); 8 } catch (Throwable var18) { 9 if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) { 10 throw (BeanCreationException)var18; 11 } 12 13 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18); 14 } 15 16 if (earlySingletonExposure) { 17 // a執行到該方法getSingleton(),這裡面會將a從二級快取中取出來 18 Object earlySingletonReference = this.getSingleton(beanName, false); 19 if (earlySingletonReference != null) { 20 // 省略大量程式碼 21 } 22 } 23 // 省略大量程式碼 24 }
繼續執行,最終也會到addSingleton()方法,將a也加入一級快取,並且從二級快取中刪除a。
這樣a和b兩個迴圈依賴的bean都被放入到一級快取中。