首頁>Club>
12
回覆列表
  • 1 # 老熊程式設計師

    (一)Spring IOC容器---物件迴圈依賴

    1. 什麼是迴圈依賴? what?

    (1)迴圈依賴-->迴圈引用。--->即2個或以上bean 互相持有對方,最終形成閉環。

    eg:A依賴B,B依賴C,C又依賴A。【注意:這裡不是函式的迴圈呼叫【是個死迴圈,除非有終結條件】,是物件相互依賴關係】

    2. Spring中迴圈依賴的場景?where?

    ①:構造器的迴圈依賴。【這個Spring解決不了】

    StudentA有參構造是StudentB。StudentB的有參構造是StudentC,StudentC的有參構造是StudentA ,這樣就產生了一個迴圈依賴的情況,

    我們都把這三個Bean交給Spring管理,並用有參構造例項化

    public class StudentA {

    private StudentB studentB ;

    public void setStudentB(StudentB studentB) {

    this.studentB = studentB;

    }

    public StudentA() {

    }

    public StudentA(StudentB studentB) {

    this.studentB = studentB;

    }

    }

    [java] view plain copy

    public class StudentB {

    private StudentC studentC ;

    public void setStudentC(StudentC studentC) {

    this.studentC = studentC;

    }

    public StudentB() {

    }

    public StudentB(StudentC studentC) {

    this.studentC = studentC;

    }

    }

    [java] view plain copy

    public class StudentC {

    private StudentA studentA ;

    public void setStudentA(StudentA studentA) {

    this.studentA = studentA;

    }

    public StudentC() {

    }

    public StudentC(StudentA studentA) {

    this.studentA = studentA;

    }

    }

    [html] view plain copy

    <bean>

    <constructor-arg index="0" ref="b"></constructor-arg>

    </bean>

    <bean>

    <constructor-arg index="0" ref="c"></constructor-arg>

    </bean>

    <bean>

    <constructor-arg index="0" ref="a"></constructor-arg>

    </bean>

    下面是測試類:

    [java] view plain copy

    public class Test {

    public static void main(String[] args) {

    ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");

    //System.out.println(context.getBean("a", StudentA.class));

    }

    }

    執行結果報錯資訊為:

    [java] view plain copy

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:

    Error creating bean with name "a": Requested bean is currently in creation: Is there an unresolvable circular reference?

    ②【setter迴圈依賴】field屬性的迴圈依賴【setter方式 單例,預設方式-->透過遞迴方法找出當前Bean所依賴的Bean,然後提前快取【會放入Cach中】起來。透過提前暴露 -->暴露一個exposedObject用於返回提前暴露的Bean。】

    前兩步驟得知:Spring是先將Bean物件例項化【依賴無參建構函式】--->再設定物件屬性的

    這就不會報錯了:

    原因:Spring先用構造器例項化Bean物件----->將例項化結束的物件放到一個Map中,並且Spring提供獲取這個未設定屬性的例項化物件的引用方法。結合我們的例項來看,,當Spring例項化了StudentA、StudentB、StudentC後,緊接著會去設定物件的屬性,此時StudentA依賴StudentB,就會去Map中取出存在裡面的單例StudentB物件,以此類推,不會出來迴圈的問題嘍。

    3. 如何檢測是否有迴圈依賴?how to find?

    可以 Bean在建立的時候給其打個標記,如果遞迴呼叫回來發現正在建立中的話--->即可說明迴圈依賴。

    4.怎麼解決的? todo what?

    Spring的迴圈依賴的理論依據其實是基於Java的引用傳遞,當我們獲取到物件的引用時,物件的field或zh屬性是可以延後設定的(但是構造器必須是在獲取引用之前)。

    Spring的單例物件的初始化主要分為三步:

    ①:createBeanInstance:例項化,其實也就是 呼叫物件的構造方法例項化物件

    ②:populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充

    從上面講述的單例bean初始化步驟我們可以知道,迴圈依賴主要發生在第一、第二步。也就是構造器迴圈依賴和field迴圈依賴。

    那麼我們要解決迴圈引用也應該從初始化過程著手,對於單例來說,在Spring容器整個生命週期內,有且只有一個物件,所以很容易想到這個物件應該存在Cache中,Spring為了解決單例的迴圈依賴問題,使用了三級快取。

    調整配置檔案,將建構函式注入方式改為 屬性注入方式 即可

    3.原始碼怎麼實現的? how?

    (1)三級快取原始碼主要 指:

    /** Cache of singleton objects: bean name --> bean instance */

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

    /** Cache of singleton factories: bean name --> ObjectFactory */

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

    /** Cache of early singleton objects: bean name --> bean instance */

    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

    這三級快取分別指:

    singletonFactories : 單例物件工廠的cache

    earlySingletonObjects :提前暴光的單例物件的Cache 。【用於檢測迴圈引用,與singletonFactories互斥】

    singletonObjects:單例物件的cache

    我們在建立bean的時候,首先想到的是從cache中獲取這個單例的bean,這個快取就是singletonObjects。主要呼叫方法就就是:

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {

    Object singletonObject = this.singletonObjects.get(beanName);

    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

    synchronized (this.singletonObjects) {

    singletonObject = this.earlySingletonObjects.get(beanName);

    if (singletonObject == null && allowEarlyReference) {

    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

    if (singletonFactory != null) {

    singletonObject = singletonFactory.getObject();

    this.earlySingletonObjects.put(beanName, singletonObject);

    this.singletonFactories.remove(beanName);

    }

    }

    }

    }

    return (singletonObject != NULL_OBJECT ? singletonObject : null);

    }

    上面的程式碼需要解釋兩個引數:

    isSingletonCurrentlyInCreation()判斷當前單例bean是否正在建立中,也就是沒有初始化完成(比如A的構造器依賴了B物件所以得先去建立B物件, 或則在A的populateBean過程中依賴了B物件,得先去建立B物件,這時的A就是處於建立中的狀態。)

    allowEarlyReference 是否允許從singletonFactories中透過getObject拿到物件

    分析getSingleton()的整個過程,Spring首先從一級快取singletonObjects中獲取。如果獲取不到,並且物件正在建立中,就再從二級快取earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories透過getObject()獲取,就從三級快取singletonFactory.getObject()(三級快取)獲取,如果獲取到了則:

    this.earlySingletonObjects.put(beanName, singletonObject);

    this.singletonFactories.remove(beanName);

    從singletonFactories中移除,並放入earlySingletonObjects中。其實也就是從三級快取移動到了二級快取。

    從上面三級快取的分析,我們可以知道,Spring解決迴圈依賴的訣竅就在於singletonFactories這個三級cache。這個cache的型別是ObjectFactory,定義如下:

    public interface ObjectFactory<T> {

    T getObject() throws BeansException;

    }

    這個介面在下面被引用

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {

    Assert.notNull(singletonFactory, "Singleton factory must not be null");

    synchronized (this.singletonObjects) {

    if (!this.singletonObjects.containsKey(beanName)) {

    this.singletonFactories.put(beanName, singletonFactory);

    this.earlySingletonObjects.remove(beanName);

    this.registeredSingletons.add(beanName);

    }

    }

    }

    這裡就是解決迴圈依賴的關鍵,這段程式碼發生在createBeanInstance之後,也就是說單例物件此時已經被創建出來(呼叫了構造器)。這個物件已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據物件引用能定位到堆中的物件),所以Spring此時將這個物件提前曝光出來讓大家認識,讓大家使用。

    這樣做有什麼好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的例項物件,同時B的某個field或者setter依賴了A的例項物件”這種迴圈依賴的情況。A首先完成了初始化的第一步,並且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴物件B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了物件A,於是嘗試get(A),嘗試一級快取singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級快取earlySingletonObjects(也沒有),嘗試三級快取singletonFactories,由於A透過ObjectFactory將自己提前曝光了,所以B能夠透過ObjectFactory.getObject拿到A物件(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A物件後順利完成了初始化階段1、2、3,完全初始化之後將自己放入到一級快取singletonObjects中。此時返回A中,A此時能拿到B的物件順利完成自己的初始化階段2、3,最終A也完成了初始化,進去了一級快取singletonObjects中,而且更加幸運的是,由於B拿到了A的物件引用,所以B現在hold住的A物件完成了初始化。

    知道了這個原理時候,肯定就知道為啥Spring不能解決“A的構造方法中依賴了B的例項物件,同時B的構造方法中依賴了A的例項物件”這類問題了!因為加入singletonFactories三級快取的前提是執行了構造器,所以構造器的迴圈依賴沒法解決

  • 2 # 淺析架構

    Spring是透過先建立物件,將物件放在快取中,再進行屬性設定的。比如A,B互相依賴,先建立A物件,放在快取中,設定屬性時發現依賴B,這時候初始化B,設定B的屬性,發現依賴A,快取中有A的引用,雖然還沒有初始化完全。B初始化完成後,A就可以拿到B了。這僅僅是解決set依賴,如果是構造器依賴就解決不了了。

    隨便說一句,一般架構設計的時候都是上層調下層,同層和下層調上層都不應該出現,可以考慮下設計是不是有問題。

  • 3 # FSRCG

    你這個問題太大了,很難簡單講清楚。簡短說Spring在InstantiateBean時執行構造器方法,構造出例項,如果是單例的話,會將它放入一個singletonBeanFactory的快取中,再進行populateBean方法,設定屬性。透過一個singletonBeanFactory的快取解決了迴圈依賴的問題。推薦你看下這個連結https://juejin.im/post/5be976a76fb9a049fd0f5f31。作者講的很細緻清楚

  • 中秋節和大豐收的關聯?
  • 宇宙真的是廣闊無邊的嗎?