您好,登錄后才能下訂單哦!
這篇“Java中的Spring怎么處理循環依賴”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java中的Spring怎么處理循環依賴”文章吧。
依賴指的是Bean與Bean之間的依賴關系,循環依賴指的是兩個或者多個Bean相互依賴,
代碼示例:
public class BeanA { private BeanB beanB; public BeanA(BeanB beanB){ this.beanB = beanB; } } public class BeanB { private BeanA beanA; public BeanB(BeanA beanA){ this.beanA = beanA; } }
配置文件:
<bean id="beanA" class="cn.itsource._01_di.BeanA" > <constructor-arg type="cn.itsource._01_di.BeanB" ref="beanB" /> </bean> <bean id="beanB" class="cn.itsource._01_di.BeanB" > <constructor-arg type="cn.itsource._01_di.BeanA" ref="beanA" /> </bean>
代碼示例:
public class BeanA { private BeanB beanB; public void setBeanB(BeanB beanB){ this.beanB = beanB; } } @Data public class BeanB { private BeanA beanA; public void setBeanA(BeanA beanA){ this.beanA = beanA; } }
配置文件
<bean id="beanA" class="cn.itsource._01_di.BeanA" > <property name="beanB" ref="beanB" /> </bean> <bean id="beanB" class="cn.itsource._01_di.BeanB"> <property name="beanA" ref="beanA" /> </bean>
循環依賴包括: 構造器注入循環依賴 set , 注入循環依賴 和 prototype模式Bean的循環依賴。Spring只解決了單例Bean的 setter 注入循環依賴,對于構造器循環依賴,和 prototype模式的循環依賴是無法解決的,在創建Bean的時候就會拋出異常 :“BeanCurrentlyInCreationException” ,
循環依賴控制開關在 AbstractRefreshableApplicationContext 容器工廠類中有定義:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext { @Nullable private Boolean allowBeanDefinitionOverriding; //是否允許循環依賴 @Nullable private Boolean allowCircularReferences; //設置循環依賴 public void setAllowCircularReferences(boolean allowCircularReferences) { this.allowCircularReferences = allowCircularReferences; }
默認情況下是允許Bean之間的循環依賴的,在依賴注入時Spring會嘗試處理循環依賴。如果將該屬性配置為“false”則關閉循環依賴,當在Bean依賴注入的時遇到循環依賴時拋出異常。可以通過如下方式關閉,但是一般都不這么做
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); //禁用循環依賴 applicationContext.setAllowCircularReferences(false); //刷新容器 applicationContext.refresh(); ...
構造器是不允許循環依賴的,動動你的小腦瓜想一想,比如:A 依賴 B ,B依賴C,C依賴A,在實例化A的時候,構造器需要注入B,然后Spirng會實例化B,此時的A屬于“正在創建”的狀態。當實例化B的時候,發現構造器需要注入C,然后去實例化C,然而實例化C的時候又需要注入A的實例,這樣就造成了一個死循環,永遠無法先實例化出某一個Bean,所以Spring遇到這里構造器循環依賴會直接拋出異常。
首先Spring會走Bean的實例化流程嘗試創建 A 的實例 ,在創建實例之間先從 “正在創建Bean池” (一個緩存Map而已)中去查找A 是否正在創建,如果沒找到,則將 A 放入 “正在創建Bean池”中,然后準備實例化構造器參數 B。
Spring會走Bean的實例化流程嘗試創建 B 的實例 ,在創建實例之間先從 “正在創建Bean池” (一個緩存Map而已)中去查找B 是否正在創建,如果沒找到,則將 B 放入 “正在創建Bean池”中,然后準備實例化構造器參數 A。
Spring會走Bean的實例化流程嘗試創建 A 的實例 ,在創建實例之間先從 “正在創建Bean池” (一個緩存Map而已)中去查找A 是否正在創建。
此時:Spring發現 A 正處于“正在創建Bean池”,表示出現構造器循環依賴,拋出異常:“BeanCurrentlyInCreationException”
下面我們以 BeanA 構造參數依賴BeanB, BeanB 構造參數依賴BeanA 為例來分析。
當Spring的IOC容器啟動,嘗試對單利的BeanA進行初始化,根據之前的分析我們知道,單利Bean的創建入口是 AbstractBeanFactory#doGetBean 在該方法中會先從單利Bean緩存中獲取,如果沒有代碼會走到:DefaultSingletonBeanRegistry#getSingleton(jString beanName, ObjectFactory<?> singletonFactory) 方法中 ,在該方法中會先對把創建的Bean加入 一個名字為 singletonsCurrentlyInCreation 的 ConcurrentHashMap中,意思是該Bean正在創建中,然后調用 ObjectFactory.getObject() 實例化Bean , 假設 BeanA 進入了該方法進行實例化:
//正在創建中的Bean private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { ...省略... //把該Bean的名字加入 singletonsCurrentlyInCreation 正在創建池 中 beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { //調用ObjectFactory創建Bean的實例 singletonObject = singletonFactory.getObject(); newSingleton = true; } ...省略... //如果singletonsCurrentlyInCreation中沒該Bean,就把該Bean存儲到singletonsCurrentlyInCreation中, //如果 singletonsCurrentlyInCreation 中有 該Bean,就報錯循環依賴異常BeanCurrentlyInCreationException //也就意味著同一個beanName進入該方法2次就會拋異常 protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
beforeSingletonCreation 方法非常關鍵 ,它會把beanName加入 singletonsCurrentlyInCreation,一個代表“正在創建中的Bean”的ConcurrentHashMap中。
如果singletonsCurrentlyInCreation中沒該beanName,就把該Bean存儲到singletonsCurrentlyInCreation中, 如果 singletonsCurrentlyInCreation 中有 該Bean,就報錯循環依賴異常BeanCurrentlyInCreationException
【注意】也就意味著同一個beanName進入該方法2次就會拋異常 , 現在BeanA已經加入了singletonsCurrentlyInCreation
我們前面分析過 ObjectFactory.getObject實例化Bean的詳細流程,這里我只是大概在復盤一下就行了。因為我們的BeanA的構造器注入了一個BeanB,所以 代碼最終會走到AbstractAutowireCapableBeanFactory#autowireConstructor ,通過構造器來實例化BeanA(在屬性注入那一章有講到 ) 。
在autowireConstructor 方法中會通過 ConstructorResolver#resolveConstructorArguments來解析構造參數,調用 BeanDefinitionValueResolver 去把 ref="beanB" 這種字符串的引用變成一個實實在在的Bean,即BeanB,所以在 BeanDefinitionValueResolver 屬性值解析器中又會去實例化BeanB,同樣會走到 DefaultSingletonBeanRegistry#getSingleton 中把BeanB加入 singletonsCurrentlyInCreation “正在創建Bean池”中,然后調用ObjectFactory.getObject實例化BeanB。
低于BeanB而已同樣需要通過構造器創建,BeanB構造器參數依賴了BeanA,也就意味著又會調用 BeanDefinitionValueResolver 去把 ref=“beanA” 這種字符串引用變成容器中的BeanA的Bean實例,然后代碼又會走到 DefaultSingletonBeanRegistry#getSingleton。然后再一次的嘗試把BeanA加入singletonsCurrentlyInCreation “正在創建Bean池”。
此時問題就來了,在最開始創建BeanA的時候它已經加入過一次“正在創建Bean” 池,這會兒實例化BeanB的時候,由于構造器參數依賴了BeanA,導致BeanA又想進入“正在創建Bean” 池 ,此時 Spring拋出循環依賴異常:
Error creating bean with name ‘beanA’: Requested bean is currently in creation: Is there an unresolvable circular reference?
到這,Spring處理構造器循環依賴的源碼分析完畢。
setter循環依賴是可以允許的。Spring是通過提前暴露未實例化完成的Bean的 ObjectFactory來實現循環依賴的,這樣做的目的是其他的Bean可以通過 ObjectFactory 引用到該Bean。
實現流程如下:
Spring創建BeanA,通過無參構造實例化,把BeanA添加到“正在創建Bean池”中,并暴露當前實例的ObjectFactory,即把ObjectFactory添加到singletonFactories(三級緩存)中,該ObjectFactory用來獲取創建中的BeanA,然后,然后通過setter注入BeanB
Spring創建BeanB,通過無參構造實例化,把BeanB添加到“正在創建Bean池”中,并暴露一個ObjectFactory,然后,然后通過setter注入BeanA
在BeanB通過setter注入BeanA時,由于BeanA 提前暴露了ObjectFactory ,通過它返回一個提前暴露一個創建中的BeanA。
然后完成BeanB的依賴注入
獲取Bean的時候走三級緩存:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { //一級緩存,存儲實例化好的Bean Object singletonObject = this.singletonObjects.get(beanName); //如果單利緩存池中沒有,但是beanName正在創建 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //獲取二級緩存,這個里面存儲的是正在創建的Bean,半成品 singletonObject = this.earlySingletonObjects.get(beanName); //如果也為空,但是允許循環依賴 if (singletonObject == null && allowEarlyReference) { //從三級緩存獲取Bean的創建工廠, ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //創建Bean的實例 singletonObject = singletonFactory.getObject(); //把Bean存儲到二級緩存 this.earlySingletonObjects.put(beanName, singletonObject); //移除三級緩存中的創建工廠 this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
我們以BeanA 通過settter依賴BeanB,BeanB通過setter 依賴BeanA為例來分析一下源碼,在之前的Bean實例化流程分析過程中我們了解到,Bean的實例化會走AbstractBeanFactory#doGetBean,然后查找單利緩存中是否有該Bean ,如果沒有就調用 DefaultSingletonBeanRegistry#getSingleton,方法會把BeanA加入 singletonsCurrentlyInCreation “創建中的Bean池”,然后調用ObjectFactory.getObject創建Bean.
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 源碼:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. //緩存中獲取Bean,解決了循環依賴問題 Object sharedInstance = getSingleton(beanName); ...緩存中沒有走下面... if (mbd.isSingleton()) { //走 DefaultSingletonBeanRegistry#getSingleton ,方法會把bean加入“正在創建bean池” //然后調用ObjectFactory實例化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; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
第一次進來,緩存中是沒有BeanA的,所有會走 getSingleton 方法,然后代碼最終會走到AbstractAutowireCapableBeanFactory#doCreateBean 方法中 。
AbstractAutowireCapableBeanFactory#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()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { //實例化Bean instanceWrapper = createBeanInstance(beanName, mbd, args); } ...省略... //如果是單利 ,如果是允許循環依賴,如果 beanName 出于創建中,已經被添加到“創建中的bean池” boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } //把ObjectFactory 添加到 singletonFactories 中。 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } try { //走依賴注入流程 populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } //緩存單利Bean的創建工廠,用于解決循環依賴 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { //singletonObjects單利緩存中是否包含Bean if (!this.singletonObjects.containsKey(beanName)) { //提前暴露ObjectFactory,把ObjectFactory放到singletonFactories中, //后面解決循環依賴,獲取Bean實例的時候會用到 this.singletonFactories.put(beanName, singletonFactory); //早期單利bean緩存中移除Bean this.earlySingletonObjects.remove(beanName); //把注冊的Bean加入registeredSingletons中 this.registeredSingletons.add(beanName); } } }
該方法中把BeanA實例化好之后,會把ObjectFactory存儲到一個 singletonFactories(HashMap)中來提前暴露Bean的創建工廠,用于解決循環依賴【重要】,然后調用 populateBean 走屬性注入流程。
屬性注入會通過BeanDefinition得到bean的依賴屬性,然后調用 AbstractAutowireCapableBeanFactory#applyPropertyValues ,把屬性應用到對象上。在applyPropertyValues 方法中最終調用 BeanDefinitionValueResolver#resolveValueIfNecessary 解析屬性值,比如:ref=“beanB” 這種字符串引用變成 對象實例的引用。
在BeanDefinitionValueResolver解析依賴的屬性值即:BeanB的時候,同樣會觸發BeanB的實例化,代碼會走到AbstractBeanFactory#doGetBean ,然后走方法 DefaultSingletonBeanRegistry#getSingleton 中把BeanB加入 singletonsCurrentlyInCreation “創建中的Bean池”,然后代碼會走到AbstractAutowireCapableBeanFactory#doCreateBean 方法中創建BeanB,
該方法中會先實例化BeanB,接著會把BeanB的ObjectFactory存儲到 singletonFactories (HashMap)中來提前暴露Bean的創建工廠,用于解決循環依賴,然后調用 populateBean 走屬性注入流程。
同樣因為BeanB通過Setter 注入了 A,所以在 populateBean 屬性注入流程中會解析 ref=“beanA” 為容器中的 BeanA 的實例。
然后會走到 AbstractBeanFactory#doGetBean 中獲取BeanA的實例。這個時候流程就不一樣了,我們先看一下 AbstractBeanFactory#doGetBean 中的代碼
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. //從緩存中獲取Bean Object sharedInstance = getSingleton(beanName); ...省略... //如果緩存中沒有Bean,就創建Bean if (mbd.isSingleton()) { 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; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
在獲取單利Bean的實例的時候是會先去單利Bean的緩存中去查看Bean是否已經存在,如果不存在,才會走DefaultSingletonBeanRegistry#getSingleton方法創建Bean。
問題是:此刻單利Bean緩存中已經有BeanA了,因為在最開始BeanA已經出于“正在創建Bean池”中了。我們先來看一下是如何從緩存獲取Bean的。
DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)源碼如下:
//allowEarlyReference :是否創建早期應用,主要用來解決循環依賴 @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock //從Map中 singletonObjects = new ConcurrentHashMap<>(256); 獲取單利Bean //【一級緩存】singletonObject緩存中是否有Bean , 它存儲的是已經實例化好的Bean Object singletonObject = this.singletonObjects.get(beanName); //如果singletonObjects中沒有Bean,但是Bean出于正在創建池中,即:Set<String> singletonsCurrentlyInCreation中有Bean, if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //【二級緩存】從早期單例對象的緩存 earlySingletonObjects 中獲取 singletonObject = this.earlySingletonObjects.get(beanName); //早期單利對象緩存中也沒有,但是允許循環依賴 if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { //【三級緩存】獲取ObjectFactory , 對象創建工廠,得到Bean創建過程中提前暴露的工廠。 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //通過工廠ObjectFactory 獲取對象實例 singletonObject = singletonFactory.getObject(); //把對象存儲到早期緩存中 this.earlySingletonObjects.put(beanName, singletonObject); //把ObjectFactory移除 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
這里就是經典的三級緩存解決Spring循環依賴。你看到了,這里會先從 singletonObjects 單利Bean緩存集合中獲取Bean(該緩存是實例化完成了的Bean),如果沒有,就從earlySingletonObjects早期對象緩存中獲取Bean(該緩存中存放的是還未實例化完成的早期Bean),如果還是沒有,就從singletonFactories中得到暴露的ObjectFactory來獲取依賴的Bean。然后放入早期緩存中。并把ObjectFactory從singletonFactories中移除。最后返回Bean的實例。
由于在實例化BeanA的時候已經把BeanA的ObjectFactory添加到了 singletonFactories 緩存中,那么這里就會走到 singletonFactory.getObject(); 方法得到BeanA的實例,并且會把BeanA存儲到 earlySingletonObjects早期單利Bean緩存中。
BeanA的實例成功返回,那么BeanB的 setter注入成功,代表BeanB實例化完成,那么BeanA的setter方法注入成功,BeanA實例化完成。
對于prototype模式下的Bean不允許循環依賴,因為 這種模式下Bean是不做緩存的,所以就沒法暴露ObjectFactory,也就沒辦法實現循環依賴。
以上就是關于“Java中的Spring怎么處理循環依賴”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。