學習spring對循環依賴的處理方式

1. 什麼是循環依賴

簡單的說就是Bean之間出現了依賴的閉環問題,例如Bean A依賴於Bean B,而Bean B也依賴於Bean A。若是不進行處理,那麼在Bean A和Bean B的建立過程當中會出現對象建立死循環而沒法正常的繼續執行下一步的代碼。邏輯示意圖以下:java

2. 如何處理循環依賴

  • 核心思想:

    使用緩存將Bean的首次建立和二次建立進行邏輯區分spring

  • 大概思路:
  1. 建立一個全局緩存Cache,用於在首次實例化Bean時緩存建立的信息。
  2. 在一個Bean實例化前先讀取緩存Cache信息,若沒有對象緩存就進行首次建立,不然就是二次建立直接返回緩存對象。
  3. 當出現Bean之間的循環依賴時,一個Bean二次建立時會直接經過緩存Cache返回首次建立的對象,從而跳出循環建立的怪圈。
  • 從思路推斷大概的代碼邏輯:
  1. 建立SimpleBean對象(@8897),以後將其放入緩存中,再進行依賴FooBean對象的建立
  2. 建立FooBean對象(@2546),以後將其放入緩存中,再進行依賴SimpleBean對象的建立
  3. 二次建立SimpleBean對象時直接從緩存中獲取SimpleBean對象(@8897),將對象(@8897)賦值到FooBean對象(@2546)中完成FooBean的建立
  4. 回到SimpleBean對象依賴FooBean對象的過程當中,將建立完成的FooBean對象(@2546)賦值到SimpleBean對象(@8897)中完成循環依賴
  • 代碼演示

    net.plaz.bean.SimpleBean.java緩存

    public class SimpleBean {
    
       private FooBean fooBean;
    
       public SimpleBean(){
          System.out.println("SimpleBean構造");
       }
    
       public FooBean getFooBean() {
          return fooBean;
       }
    
       public void setFooBean(FooBean fooBean) {
          this.fooBean = fooBean;
       }
    }
    複製代碼

    net.plaz.bean.FooBean.javaapp

    public class FooBean {
    
       private SimpleBean simpleBean;
    
       public FooBean(){
          System.out.println("FooBean構造");
       }
    
       public void setSimpleBean(SimpleBean simpleBean) {
          this.simpleBean = simpleBean;
       }
    
       public SimpleBean getSimpleBean() {
          return simpleBean;
       }
    }
    複製代碼

    net.plaz.bean.SpringFactory.javaide

    public class SpringFactory {
    
       //bean緩存(<'net.plaz.bean.FooBean', new FooBean()>)
       private Map<String, Object> earlySingletonCacheMap = new HashMap<String, Object> ();
    
       //使用needWiredMap模擬實例化對象的須要注入屬性
       //<'net.plaz.bean.FooBean', 'net.plaz.bean.SimpleBean'>表示FooBean須要注入一個SimpleBean
       private Map<String, String> needWiredMap = new HashMap<String, String> ();
    
    
       public Object getBeanInstance(String className){
          //先從緩存中獲取,若獲取到,則直接返回
          Object obj = earlySingletonCacheMap.get(className);
          if(obj == null){
             //緩存中沒有對象,則要新建立一個對象,並進行屬性賦值
             try {
    
                Class<?> clazz = Class.forName(className);
                obj = clazz.newInstance();
                //將建立的對象加入緩存中
                earlySingletonCacheMap.put(className, obj);
                //以後進行屬性賦值
                //獲取要注入的val(遞歸建立對象)
                String propertyStr = needWiredMap.get(className);
                Object propertyObj = getBeanInstance(propertyStr);
                //key
                String propertyKey = getPropertyName(propertyStr);
                //將val經過set方法對po的屬性賦值
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(propertyKey, obj.getClass());
                propertyDescriptor.getWriteMethod().invoke(obj, propertyObj);
    
             } catch (ClassNotFoundException e) {
                e.printStackTrace();
             } catch (IllegalAccessException e) {
                e.printStackTrace();
             } catch (InstantiationException e) {
                e.printStackTrace();
             } catch (IntrospectionException e) {
                e.printStackTrace();
             } catch (InvocationTargetException e) {
                e.printStackTrace();
             }
          }
    
          return obj;
       }
    
       /** * 功能描述: 獲取屬性名稱 * <br> * @Param: [className] * @Return: java.lang.String */
       private String getPropertyName(String className){
          String[] strArr = className.split("\\.");
          String prop = strArr[strArr.length -1];
          char[] chars = prop.toCharArray();
          chars[0] += 32;
          return String.valueOf(chars);
       }
    
       //下面是set/get方法
       public Map<String, Object> getEarlySingletonCacheMap() {
          return earlySingletonCacheMap;
       }
    
       public void setEarlySingletonCacheMap(Map<String, Object> earlysingletonCacheMap) {
          this.earlySingletonCacheMap = earlysingletonCacheMap;
       }
    
    
       public Map<String, String> getNeedWiredMap() {
          return needWiredMap;
       }
    
       public void setNeedWiredMap(Map<String, String> needWiredMap) {
          this.needWiredMap = needWiredMap;
       }
    }
    複製代碼

    代碼調用函數

    public static void main(String[] args) {
       //要注入的屬性(模擬bean依賴關係)
       //<'net.plaz.bean.FooBean', 'net.plaz.bean.SimpleBean'>表示FooBean須要注入一個SimpleBean
       Map<String, String> needWiredMap = new HashMap<String, String> ();
       needWiredMap.put("net.plaz.bean.SimpleBean", "net.plaz.bean.FooBean");
       needWiredMap.put("net.plaz.bean.FooBean", "net.plaz.bean.SimpleBean");
    
       //建立bean工廠
       SpringFactory factory = new SpringFactory();
       //設置bean關係
       factory.getNeedWiredMap().putAll(needWiredMap);
    
       //模擬要實例化的列表
       List<String> beanList = new ArrayList<>();
       beanList.add("net.plaz.bean.SimpleBean");
       beanList.add("net.plaz.bean.FooBean");
    
       List<Object> result = new ArrayList<> ();
       for(String bean : beanList){
          result.add(factory.getBeanInstance(bean));
       }
       System.out.println(result);
    }
    
    //執行結果
    //成功的處理了循環依賴(●'◡'●)
    SimpleBean構造
    FooBean構造
    [net.plaz.bean.SimpleBean@2344fc66, net.plaz.bean.FooBean@458ad742]
    複製代碼

代碼執行邏輯以下:學習

  1. 先建立simpleBean空對象,將對象放入未實例化完成的緩存中便於其餘對象調用ui

  2. 以後對其依賴對象FooBean進行實例化,一樣將FooBean空對象放入未實例化完成的緩存中this

  3. 在FooBean實例化中進行填充對象屬性時,又要建立simpleBean對象;此時simpleBean處於建立中的bean列表中,同時在未實例化完成的緩存中已經有一個simpleBean對象,因此直接將步驟1建立的simpleBean對象引用賦值到FooBean對象的屬性中lua

  4. 在FooBean建立完成後回到步驟2,將建立完成的FooBean對象引用賦值給simpleBean對象完成實例化

3. spring如何處理循環依賴

在spring DI過程當中,對於循環依賴的處理方式和上述處理基本相同(上述處理屬於spring的簡化版)。下面來扒下spring的源碼學習下大體的流程:

1.咱們一般使用getBean(java.lang.Class<T>)從IOC中獲取bean信息,實際上在IOC容器經過掃描包或加載XML後也會循環調用getBean(...)進行Bean的首輪實例化(有興趣詳見DefaultListableBeanFactory#preInstantiateSingletons())。下面來詳細瞭解下getBean(...)中對於循環依賴的處理。

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
//doGetBean是getBean方法的實際邏輯方法,這裏只貼出了相關的部分代碼
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
   //處理bean名稱的規範問題
   final String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   //從緩存中獲取bean實例
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      if (logger.isTraceEnabled()) {
         if (isSingletonCurrentlyInCreation(beanName)) {
            logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                  "' that is not fully initialized yet - a consequence of a circular reference");
         }
         else {
            logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
         }
      }
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }

   else {
	  //省略...

      try {
         //獲取beanName對應的BeanDefinition
         final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
		//省略...
          
         // Create bean instance.
         //根據bean的做用域來建立bean實例
         if (mbd.isSingleton()) {
            //建立單例模式的bean
            sharedInstance = getSingleton(beanName, () -> {
               try {
                   //單例的bean實例化方法
                  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;
               }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }
         else if (mbd.isPrototype()) {
            //建立原型模式bean
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
               beforePrototypeCreation(beanName);
               prototypeInstance = createBean(beanName, mbd, args);
            }
            finally {
               afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
         }else {
             //建立其餘模式bean
            String scopeName = mbd.getScope();
            final Scope scope = this.scopes.get(scopeName);
            if (scope == null) {
               throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
            }
            try {
               Object scopedInstance = scope.get(beanName, () -> {
                  beforePrototypeCreation(beanName);
                  try {
                     return createBean(beanName, mbd, args);
                  }
                  finally {
                     afterPrototypeCreation(beanName);
                  }
               });
               bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            }
            catch (IllegalStateException ex) {
               throw new BeanCreationException(beanName,
                     "Scope '" + scopeName + "' is not active for the current thread; consider " +
                     "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                     ex);
            }
         }
      }
      catch (BeansException ex) {
         cleanupAfterBeanCreationFailure(beanName);
         throw ex;
      }
   }

   //省略...
   return (T) bean;
}
複製代碼

上述doGetBean大體作了幾個步驟:

  1. 嘗試根據beanName從緩存中獲取獲取bean對象
  2. 若獲取到緩存對象則執行getObjectForBeanInstance(...)後返回bean信息
  3. 若沒有獲取到緩存對象(首次建立),則根據bean的做用域類型來採起不一樣方式建立bean(這裏默認爲單例模式),而後再執行getObjectForBeanInstance(...)後返回bean信息

其中涉及到循環依賴的處理有getSingleton(beanName)先獲取緩存對象:

//DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //從singletonObjects(存儲已完成實例化的單例對象)緩存中獲取
   Object singletonObject = this.singletonObjects.get(beanName);
    //沒有獲取到bean,判斷當前beanName對象是否在建立中
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         //從earlySingletonObjects(存儲剛實例化的單例對象)緩存中獲取==>循環依賴的一種緩存對象
         singletonObject = this.earlySingletonObjects.get(beanName);
         //沒有獲取到bean,判斷是否容許提早引用其餘bean
         if (singletonObject == null && allowEarlyReference) {
             //從singletonFactories緩存中獲取==>循環依賴的一種緩存對象
             //獲取到的對象是一個函數式接口對象,能直接獲取到beanName首次建立的對象
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                //singletonFactories有,earlySingletonObjects沒有,則將對象從singletonFactories挪到earlySingletonObjects
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}
複製代碼

2.這裏咱們的bean按照單例模式,走首次建立路徑createBean(beanName, mbd, args);,而createBean(beanName, mbd, args);中真正的邏輯方法是doCreateBean(...),下面咱們看下doCreateBean(...)的方法:

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

   // Instantiate the bean.
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      //根據beanName將當前對象從未完成實例化列表緩存中移除並返回
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   //若未完成實例化列表緩存中沒有數據則建立一個空對象
   if (instanceWrapper == null) {
      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.
   //將bean寫入提早暴露的緩存中(此時的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");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
      //將beandefinition中的屬性寫入對應的instanceWrapper對象實例中
      //依賴循環就是在這裏處理的 
      populateBean(beanName, mbd, instanceWrapper);
      //若是exposedObject對象有實現一些aware、init接口則初始化這些接口
      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);
      }
   }
   //省略...

   return exposedObject;
}
複製代碼

doCreateBean(...)的主要邏輯有如下幾步:

  1. 建立一個bean的包裝對象instanceWrapper(實際爲Class.forName(className).newInstance()建立,有興趣自行可跟蹤代碼)
  2. 經過addSingletonFactory(...)將剛實例化的對象放入緩存中
  3. populateBean(...)中處理bean對象的依賴屬性(在這裏遞歸調用其餘依賴的bean)
  4. initializeBean(...)中調用對象的一些初始化接口(如實現InitializingBean),並返回結果bean

涉及循環依賴的處理有addSingletonFactory(...)populateBean(...)兩部分,咱們先看下addSingletonFactory(...)將bean加入緩存中:

//org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      //沒有建立過beanName的bean則加入緩存
      if (!this.singletonObjects.containsKey(beanName)) {
         //存儲在singletonFactories中,在getSingleton(...)中獲取調用
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}

//參數ObjectFactory<?> singletonFactory是一個函數式接口對象
//內容爲() -> getEarlyBeanReference(beanName, mbd, bean)
//調用singletonFactory會執行getEarlyBeanReference(beanName, mbd, bean),返回bean的首次建立對象
//實際上會在獲取緩存對象的getSingleton(...)中調用 singletonFactory.getObject();
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	//忽略...
	return exposedObject;
}
複製代碼

3.而populateBean(...)是根據BeanDefinition將屬性賦值到剛建立的對象中,主要的邏輯在applyPropertyValues(...)中執行,大體代碼以下:

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
   //省略...
    //建立屬性解析器(主要完成屬性值的處理,包括依賴其餘bean的建立)
   BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

   // Create a deep copy, resolving any references for values.
   List<PropertyValue> deepCopy = new ArrayList<>(original.size());
   boolean resolveNecessary = false;
   for (PropertyValue pv : original) {
      if (pv.isConverted()) {
         deepCopy.add(pv);
      }
      else {
		 //獲取屬性名稱
		 String propertyName = pv.getName();
		 Object originalValue = pv.getValue();
		 //使用解析器解析不一樣類型的值
		 Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
          //將值包裝到deepCopy的list中
         Object convertedValue = resolvedValue;
         boolean convertible = bw.isWritableProperty(propertyName) &&
               !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
         if (convertible) {
            convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
         }
         // Possibly store converted value in merged bean definition,
         // in order to avoid re-conversion for every created bean instance.
         if (resolvedValue == originalValue) {
            if (convertible) {
               pv.setConvertedValue(convertedValue);
            }
            deepCopy.add(pv);
         }
         else if (convertible && originalValue instanceof TypedStringValue &&
               !((TypedStringValue) originalValue).isDynamic() &&
               !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
            pv.setConvertedValue(convertedValue);
            deepCopy.add(pv);
         }
         else {
            resolveNecessary = true;
            deepCopy.add(new PropertyValue(pv, convertedValue));
         }
      }
   }
   if (mpvs != null && !resolveNecessary) {
      mpvs.setConverted();
   }

   // Set our (possibly massaged) deep copy.
   try {
       //將屬性賦值到對象中
      bw.setPropertyValues(new MutablePropertyValues(deepCopy));
   }
   catch (BeansException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Error setting property values", ex);
   }
}
複製代碼

主要邏輯是以下:

  1. 建立屬性解析器valueResolver, 以後循環BeanDefinition中的屬性列表,使用解析器對每一個property進行實際值的解析(保存建立依賴bean對象)
  2. 根據屬性的名稱將屬性值賦值到對象中

4.涉及到循環依賴的邏輯是valueResolver.resolveValueIfNecessary(pv, originalValue),使用屬性解析器獲取property的實際內容,下面咱們看下如何解析property的(只看依賴其餘bean的property):

//org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveValueIfNecessary
@Nullable
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
   // We must check each value to see whether it requires a runtime reference
   // to another bean to be resolved.
    //處理依賴其餘bean的property
   if (value instanceof RuntimeBeanReference) {
      RuntimeBeanReference ref = (RuntimeBeanReference) value;
      return resolveReference(argName, ref);
   }
    //省略...
}

//詳細處理邏輯
@Nullable
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
	try {
		Object bean;
        //獲取依賴bean名稱
		String refName = ref.getBeanName();
		refName = String.valueOf(doEvaluate(refName));
        //依賴是否屬於父容器
		if (ref.isToParent()) {
			if (this.beanFactory.getParentBeanFactory() == null) {
				throw new BeanCreationException(
						this.beanDefinition.getResourceDescription(), this.beanName,
						"Can't resolve reference to bean '" + refName +
								"' in parent factory: no parent factory available");
			}
			bean = this.beanFactory.getParentBeanFactory().getBean(refName);
		}
		else {
            //嵌套調用IOC容器的getBean方法
			bean = this.beanFactory.getBean(refName);
			this.beanFactory.registerDependentBean(refName, this.beanName);
		}
		if (bean instanceof NullBean) {
			bean = null;
		}
		return bean;
	}
	catch (BeansException ex) {
		throw new BeanCreationException(
				this.beanDefinition.getResourceDescription(), this.beanName,
				"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
	}
}
複製代碼

上述邏輯比較清晰簡單,就是根據依賴的beanName嵌套調用this.beanFactory.getBean(refName)去建立所依賴對象,建立完成後返回該bean信息。

  • 總結:

    到這裏咱們就能夠大體的明白spring是如何處理依賴循環的了:

  1. 調用getBean(...)方法建立一個bean,前先從緩存getSingleton(...)中獲取對象信息
  2. 如果沒有緩存,則首次建立後將其對象加入到緩存中
  3. 以後對建立的對象進行屬性填充populateBean(...),填充過程當中建立屬性解析器對bean的屬性進行處理
  4. 若屬性類型依賴其餘的bean,則會嵌套調用IOC容器的getBean方法去建立所依賴的bean對象,直到出現從緩存中獲取到對象後跳出嵌套邏輯,才能夠完成整個bean的屬性賦值過程。

4. spring中循環依賴的的一些思考

上述所spring處理的循環依賴有兩個關鍵點:

  1. 建立的bean是單例模式的,

  2. 是經過屬性的set方法對其依賴bean進行賦值的。

因此思考如下幾個問題:

  1. Q:若bean類型是原型模式(prototye)是否可適用這樣的循環依賴處理方式?

    S:不能夠,原型模式每次建立的bean都是新對象,沒法保證對象統一,spring也不會緩存原型模式的bean

  2. Q:處理循環依賴中,是否能夠經過構造方法進行依賴注入?

    S:不能夠,set方法是在對象建立後進行的,而構造方法是在對象建立中執行的,此時beanA是沒有緩存信息的,只能在建立以前就獲取到依賴的beanB對象,而依賴的beanB再次嵌套建立beanA時,因爲beanA處於建立中的狀態,就會報錯。

  3. Q:除了經過set方法進行依賴注入外,還能夠經過其餘方法完成依賴注入嗎?

    S:只要是在對象建立以後進行bean賦值基本均可實現,下面介紹幾種:

    • 使用@Autowired和@PostConstruct在初始化方法中進行賦值

    • 實現ApplicationContextAware和InitializingBean接口在afterPropertiesSet()方法中調用getBean()賦值

    • 使用@Lazy註解,先注入代理對象,在後面首次使用時完成賦值

相關文章
相關標籤/搜索