การพึ่งพาแบบวงกลมในฤดูใบไม้ผลิ


101

Spring แก้ปัญหานี้อย่างไร: bean A ขึ้นอยู่กับ bean B และ bean B บน bean A



บทความที่มีประโยชน์อีกเรื่องหนึ่งที่อธิบายว่าการอ้างอิงแบบวงกลมเกิดขึ้นได้อย่างไร: octoperf.com/blog/2018/02/15/spring-circular-dependencies
Jerome L

คำตอบ:


42

ดังที่คำตอบอื่น ๆ ได้กล่าวไว้ว่า Spring เพียงแค่ดูแลมันสร้างเมล็ดถั่วและฉีดเข้าไปตามต้องการ

ผลที่ตามมาประการหนึ่งคือการตั้งค่า bean injection / property อาจเกิดขึ้นในลำดับที่แตกต่างจากสิ่งที่ไฟล์การเดินสาย XML ของคุณดูเหมือนจะบ่งบอก ดังนั้นคุณต้องระวังว่าตัวตั้งค่าคุณสมบัติของคุณไม่ได้ทำการเริ่มต้นที่อาศัยตัวตั้งค่าอื่นที่ถูกเรียกไปแล้ว วิธีจัดการกับปัญหานี้คือประกาศ bean ว่าใช้InitializingBeanอินเทอร์เฟซ สิ่งนี้ต้องการให้คุณใช้afterPropertiesSet()วิธีนี้และนี่คือที่ที่คุณทำการเริ่มต้นที่สำคัญ (ฉันยังใส่รหัสเพื่อตรวจสอบว่ามีการตั้งค่าคุณสมบัติที่สำคัญจริง)


76

คู่มืออ้างอิงฤดูใบไม้ผลิอธิบายถึงวิธีการอ้างอิงแบบวงกลมได้รับการแก้ไข ถั่วจะถูกสร้างอินสแตนซ์ก่อนจากนั้นจึงฉีดเข้าไปในกันและกัน

พิจารณาคลาสนี้:

package mypackage;

public class A {

    public A() {
        System.out.println("Creating instance of A");
    }

    private B b;

    public void setB(B b) {
        System.out.println("Setting property b of A instance");
        this.b = b;
    }

}

และคลาสที่คล้ายกันB:

package mypackage;

public class B {

    public B() {
        System.out.println("Creating instance of B");
    }

    private A a;

    public void setA(A a) {
        System.out.println("Setting property a of B instance");
        this.a = a;
    }

}

หากคุณมีไฟล์กำหนดค่านี้:

<bean id="a" class="mypackage.A">
    <property name="b" ref="b" />
</bean>

<bean id="b" class="mypackage.B">
    <property name="a" ref="a" />
</bean>

คุณจะเห็นผลลัพธ์ต่อไปนี้เมื่อสร้างบริบทโดยใช้การกำหนดค่านี้:

Creating instance of A
Creating instance of B
Setting property a of B instance
Setting property b of A instance

ทราบว่าเมื่อaถูกฉีดเข้าไปในb, aยังไม่ได้เริ่มต้นใช้งานอย่างเต็มที่


26
นี่คือเหตุผลที่ Spring ต้องการตัวสร้างโดยไม่มีข้อโต้แย้ง ;-)
Chris Thompson

15
ไม่ใช่ถ้าคุณใช้อาร์กิวเมนต์ตัวสร้างในนิยาม bean ของคุณ! (แต่ในกรณีนั้นคุณไม่สามารถมีการพึ่งพาแบบวงกลมได้)
Richard Fearn

1
@Richard Fearn โพสต์ของคุณเกี่ยวกับคำอธิบายปัญหาแทนที่จะให้วิธีแก้ปัญหาหรือไม่?
gstackoverflow

4
หากคุณพยายามใช้ constructor injection ข้อความแสดงข้อผิดพลาดคือorg.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
X Wo Satuk

19

ใน codebase ฉันกำลังใช้งาน (โค้ดมากกว่า 1 ล้านบรรทัด) เรามีปัญหากับเวลาเริ่มต้นที่ยาวนานประมาณ 60 วินาที เราได้รับมากกว่า 12000+ FactoryBeanNotInitializedException FactoryBeanNotInitializedException

สิ่งที่ฉันทำคือตั้งค่าเบรกพอยต์แบบมีเงื่อนไขในAbstractBeanFactory # doGetBean

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;
}

destroySingleton(beanName)ฉันพิมพ์ข้อยกเว้นพร้อมรหัสเบรกพอยต์ตามเงื่อนไขที่ไหน:

   System.out.println(ex);
   return false;

เห็นได้ชัดว่าสิ่งนี้เกิดขึ้นเมื่อFactoryBeanมีส่วนร่วมในกราฟการพึ่งพาแบบวนรอบ เราแก้ไขได้โดยใช้ApplicationContextAwareและ InitializingBeanและฉีดถั่วด้วยตนเอง

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class A implements ApplicationContextAware, InitializingBean{

    private B cyclicDepenency;
    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        ctx = applicationContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        cyclicDepenency = ctx.getBean(B.class);
    }

    public void useCyclicDependency()
    {
        cyclicDepenency.doSomething();
    }
}

ซึ่งจะลดเวลาในการเริ่มต้นลงเหลือประมาณ 15 วินาที

ดังนั้นอย่าคิดเสมอไปว่าฤดูใบไม้ผลิสามารถแก้ข้อมูลอ้างอิงเหล่านี้ให้คุณได้ดี

ด้วยเหตุนี้ฉันขอแนะนำให้ปิดใช้งานการแก้ปัญหาการอ้างอิงแบบวนรอบด้วยAbstractRefreshableApplicationContext # setAllowCircularReferences (false)เพื่อป้องกันปัญหาในอนาคตมากมาย


3
คำแนะนำที่น่าสนใจ คำแนะนำโต้แย้งของฉันจะทำได้ก็ต่อเมื่อคุณสงสัยว่าการอ้างอิงแบบวงกลมทำให้เกิดปัญหาด้านประสิทธิภาพ (คงเป็นเรื่องน่าเสียดายที่จะทำลายบางสิ่งที่ไม่จำเป็นต้องเสียโดยพยายามแก้ไขปัญหาที่ไม่ต้องการการแก้ไข)
Stephen C

2
มันเป็นความลาดชันที่ลื่นลงไปจนถึงนรกแห่งการบำรุงรักษาเพื่อให้สามารถพึ่งพาแบบวงกลมได้การออกแบบสถาปัตยกรรมของคุณใหม่จากการอ้างอิงแบบวงกลมอาจเป็นเรื่องยากอย่างที่เป็นอยู่ในกรณีของเรา สิ่งที่มีความหมายโดยคร่าวๆสำหรับเราคือเรามีการเชื่อมต่อฐานข้อมูลมากขึ้นเป็นสองเท่าในระหว่างการเริ่มต้นเนื่องจาก sessionfactory เกี่ยวข้องกับการพึ่งพาแบบวงกลม ในสถานการณ์อื่น ๆ อาจเกิดหายนะขึ้นได้เนื่องจากถั่วถูกสร้างอินสแตนซ์มากกว่า 12000 ครั้ง แน่ใจว่าคุณควรเขียนถั่วของคุณเพื่อสนับสนุนการทำลายมัน แต่ทำไมถึงยอมให้มีพฤติกรรมเช่นนี้ตั้งแต่แรก?
jontejj

@jontejj คุณสมควรได้รับคุกกี้
serprime

14

ปัญหา ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(B b) { this.b = b };
}


Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
 }

// เกิดจาก: org.springframework.beans.factory BeanCurrentlyInCreationException: เกิดข้อผิดพลาดในการสร้าง bean ด้วยชื่อ 'A': Requested bean อยู่ระหว่างการสร้าง: มีการอ้างอิงแบบวงกลมที่ไม่สามารถแก้ไขได้หรือไม่?

โซลูชันที่ 1 ->

Class A {
    private B b; 
    public A( ) {  };
    //getter-setter for B b
}

Class B {
    private A a;
    public B( ) {  };
    //getter-setter for A a
}

โซลูชันที่ 2 ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(@Lazy B b) { this.b = b };
}

Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
}

12

มันก็ทำมัน มันอินสแตนซ์aและbและฉีดแต่ละอันเข้าไปในอีกอันหนึ่ง (โดยใช้วิธีเซ็ตเตอร์)

มีปัญหาอะไร?


9
@javaguy: ไม่มันจะไม่
skaffman

@skaffman วิธีเดียวกับ after propertiesSet วิธีการใช้งานที่เหมาะสม?
gstackoverflow

6

จากSpring Reference :

โดยทั่วไปคุณสามารถวางใจให้ Spring ทำในสิ่งที่ถูกต้องได้ ตรวจพบปัญหาการกำหนดค่าเช่นการอ้างอิงถึงถั่วที่ไม่มีอยู่จริงและการอ้างอิงแบบวงกลมในเวลาโหลดคอนเทนเนอร์ Spring กำหนดคุณสมบัติและแก้ไขการอ้างอิงให้ช้าที่สุดเมื่อสร้าง bean ขึ้นมาจริงๆ


6

Spring container สามารถแก้ปัญหาการอ้างอิงแบบวงกลมที่ใช้ Setter แต่ให้ข้อยกเว้นรันไทม์ BeanCurrentlyInCreationException ในกรณีของการอ้างอิงแบบวงกลมที่ใช้ตัวสร้าง ในกรณีของการพึ่งพาแบบวงกลมตาม Setter คอนเทนเนอร์ IOC จะจัดการกับมันแตกต่างจากสถานการณ์ทั่วไปซึ่งจะกำหนดค่า bean การทำงานร่วมกันอย่างสมบูรณ์ก่อนที่จะฉีด ตัวอย่างเช่นถ้า Bean A ขึ้นอยู่กับ Bean B และ Bean B บน Bean C คอนเทนเนอร์จะเริ่มต้น C อย่างสมบูรณ์ก่อนที่จะฉีดไปที่ B และเมื่อ B เริ่มต้นอย่างสมบูรณ์แล้วจะถูกฉีดไปที่ A แต่ในกรณีของการพึ่งพาแบบวงกลมหนึ่ง ของถั่วจะถูกฉีดไปที่อื่นก่อนที่จะเริ่มต้นอย่างสมบูรณ์


5

พูดว่า A ขึ้นอยู่กับ B จากนั้น Spring จะสร้างอินสแตนซ์ A ก่อนตามด้วย B จากนั้นตั้งค่าคุณสมบัติสำหรับ B จากนั้นตั้งค่า B เป็น A

แต่ถ้า B ขึ้นอยู่กับ A ล่ะ?

ความเข้าใจของฉันคือ: ฤดูใบไม้ผลิเพิ่งพบว่า A ถูกสร้างขึ้น (ตัวสร้างดำเนินการ) แต่ไม่ได้เริ่มต้นอย่างสมบูรณ์ (ไม่ใช่การฉีดทั้งหมดที่เสร็จสิ้น) คิดว่ามันก็โอเคเป็นที่ยอมรับได้ว่า A ไม่ได้เริ่มต้นอย่างสมบูรณ์เพียงแค่ตั้งค่านี้ไม่ใช่ - เริ่มต้นอย่างสมบูรณ์อินสแตนซ์ A เป็น B ในตอนนี้ หลังจาก B เริ่มต้นอย่างสมบูรณ์แล้วจะถูกตั้งค่าเป็น A และในที่สุด A ก็เริ่มต้นอย่างสมบูรณ์ในขณะนี้

กล่าวอีกนัยหนึ่งก็คือแสดง A ถึง B ล่วงหน้า

สำหรับการอ้างอิงผ่านตัวสร้าง Sprint เพียงแค่โยน BeanCurrentlyInCreationException เพื่อแก้ไขข้อยกเว้นนี้ตั้งค่า lazy-init เป็น true สำหรับ bean ซึ่งขึ้นอยู่กับผู้อื่นผ่านทาง constructor-arg


ง่ายและเป็นหนึ่งในคำอธิบายที่ดีที่สุด
Sritam Jagadev

5

มันอธิบายอย่างชัดเจนที่นี่ ขอบคุณ Eugen Paraschiv

การพึ่งพาแบบวงกลมเป็นกลิ่นการออกแบบไม่ว่าจะแก้ไขหรือใช้ @Lazy สำหรับการพึ่งพาซึ่งทำให้เกิดปัญหาในการแก้ไขปัญหา


3

หากโดยทั่วไปคุณใช้ constructor-injection และไม่ต้องการเปลี่ยนไปใช้ property-injection การค้นหาวิธีการ - การฉีดของ Spring จะช่วยให้ถั่วหนึ่งตัวค้นหาอีกอันหนึ่งอย่างเกียจคร้านและด้วยเหตุนี้วิธีแก้ปัญหาการพึ่งพาแบบวัฏจักร ดูที่นี่: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161


3

Constructor Injection ล้มเหลวเมื่อมี Circular Dependency ระหว่างถั่วสปริง ดังนั้นในกรณีนี้การฉีด Setter จะช่วยแก้ไขปัญหา

โดยพื้นฐานแล้ว Constructor Injection มีประโยชน์สำหรับการพึ่งพาที่จำเป็นสำหรับการพึ่งพาทางเลือกที่ดีกว่าที่จะใช้ Setter injection เนื่องจากเราสามารถทำการฉีดซ้ำได้


0

หากถั่วสองเมล็ดขึ้นอยู่กับกันและกันเราไม่ควรใช้ Constructor injection ในทั้งสองคำจำกัดความของถั่ว เราต้องใช้ setter injection ในถั่วชนิดใดชนิดหนึ่งแทน (แน่นอนว่าเราสามารถใช้ setter injection n ทั้งคำจำกัดความของ bean แต่การฉีดตัวสร้างในทั้งสองโยน 'BeanCurrentlyInCreationException'

อ้างอิง Spring doc ที่ " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource "

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.