Ketergantungan melingkar di Spring

Jawaban:

42

Seperti yang dikatakan jawaban lain, Spring hanya mengurusnya, membuat kacang dan menyuntikkannya sesuai kebutuhan.

Salah satu konsekuensinya adalah pengaturan injeksi kacang / properti mungkin terjadi dalam urutan yang berbeda dengan apa yang tampaknya disiratkan oleh file kabel XML Anda. Jadi, Anda perlu berhati-hati agar penyetel properti Anda tidak melakukan inisialisasi yang bergantung pada penyetel lain yang sudah dipanggil. Cara untuk mengatasinya adalah dengan mendeklarasikan beans sebagai implementasi InitializingBeanantar muka. Ini mengharuskan Anda untuk mengimplementasikan afterPropertiesSet()metode, dan di sinilah Anda melakukan inisialisasi kritis. (Saya juga menyertakan kode untuk memeriksa bahwa properti penting sebenarnya telah disetel.)

Stephen C
sumber
76

The pengguna Musim Semi referensi menjelaskan bagaimana dependensi melingkar diselesaikan. Kacang dibuat terlebih dahulu, lalu disuntikkan satu sama lain.

Pertimbangkan kelas ini:

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

}

Dan kelas serupa 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;
    }

}

Jika Anda kemudian memiliki file konfigurasi ini:

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

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

Anda akan melihat keluaran berikut saat membuat konteks menggunakan konfigurasi ini:

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

Perhatikan bahwa saat adisuntikkan ke b, abelum sepenuhnya diinisialisasi.

Richard Fearn
sumber
26
Inilah sebabnya mengapa Spring membutuhkan konstruktor tanpa argumen ;-)
Chris Thompson
15
Tidak jika Anda menggunakan argumen konstruktor dalam definisi kacang Anda! (Tapi dalam hal ini Anda tidak dapat memiliki ketergantungan melingkar.)
Richard Fearn
1
@Richard Fearn Apakah posting Anda tentang penjelasan masalah daripada menyediakan solusi?
gstackoverflow
4
Jika Anda mencoba menggunakan injeksi konstruktor, pesan kesalahannya adalahorg.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

Dalam basis kode tempat saya bekerja (1 juta + baris kode) kami memiliki masalah dengan waktu startup yang lama, sekitar 60 detik. Kami mendapatkan 12000+ FactoryBeanNotInitializedException .

Apa yang saya lakukan adalah menetapkan breakpoint bersyarat di 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;
}

di mana destroySingleton(beanName)saya mencetak pengecualian dengan kode breakpoint bersyarat:

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

Rupanya ini terjadi ketika FactoryBean terlibat dalam grafik ketergantungan siklik. Kami menyelesaikannya dengan mengimplementasikan ApplicationContextAware dan InitializingBean dan menyuntikkan kacang secara manual.

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

Ini memotong waktu startup menjadi sekitar 15 detik.

Jadi jangan selalu berasumsi bahwa musim semi bisa menjadi solusi yang baik untuk memecahkan referensi ini untuk Anda.

Untuk alasan ini saya merekomendasikan untuk menonaktifkan resolusi ketergantungan siklik dengan AbstractRefreshableApplicationContext # setAllowCircularReferences (false) untuk mencegah banyak masalah di masa mendatang.

jontejj
sumber
3
Rekomendasi menarik. Rekomendasi balasan saya hanya akan melakukan itu jika Anda mencurigai bahwa referensi melingkar menyebabkan masalah kinerja. (Sayang sekali jika memecahkan sesuatu yang tidak perlu dipecahkan dengan mencoba memperbaiki masalah yang tidak perlu diperbaiki.)
Stephen C
2
Ini adalah lereng licin menuju neraka pemeliharaan untuk memungkinkan ketergantungan melingkar, mendesain ulang arsitektur Anda dari ketergantungan melingkar bisa sangat rumit, seperti dalam kasus kami. Apa yang dimaksud secara kasar bagi kami adalah kami mendapatkan koneksi database dua kali lebih banyak selama startup karena sessionfactory yang terlibat dalam dependensi melingkar. Dalam skenario lain, jauh lebih banyak bencana dapat terjadi karena kacang dibuat lebih dari 12000 kali. Tentu Anda harus menulis kacang Anda sehingga mereka mendukung penghancurannya tetapi mengapa membiarkan perilaku ini di tempat pertama?
jontejj
@jontejj, Anda berhak mendapatkan cookie
serprime
14

Masalah ->

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

// Disebabkan oleh: org.springframework.beans.factory.BeanCurrentlyInCreationException: Kesalahan membuat kacang dengan nama 'A': Kacang yang diminta saat ini sedang dibuat: Apakah ada referensi melingkar yang tidak dapat diselesaikan?

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

Solusi 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 };
}
Akshay N. Shelke
sumber
12

Itu hanya melakukannya. Ini membuat instance adan b, dan menyuntikkan masing-masing satu sama lain (menggunakan metode penyetelnya).

Apa masalahnya?

skaffman
sumber
9
@javaguy: Tidak, tidak akan.
Skaffman
@skaffman hanya dengan cara setelah penggunaan metode propertiesSet yang tepat?
gstackoverflow
6

Dari Referensi Musim Semi :

Biasanya Anda dapat mempercayai Spring untuk melakukan hal yang benar. Ini mendeteksi masalah konfigurasi, seperti referensi ke kacang yang tidak ada dan dependensi melingkar, pada waktu muat kontainer. Spring mengatur properti dan menyelesaikan dependensi selambat mungkin, ketika bean sebenarnya dibuat.

earldouglas
sumber
6

Kontainer Spring mampu menyelesaikan dependensi melingkar berbasis Setter tetapi memberikan pengecualian runtime BeanCurrentlyInCreationException dalam kasus dependensi melingkar berbasis Constructor. Dalam kasus ketergantungan melingkar berbasis Setter, kontainer IOC menanganinya secara berbeda dari skenario tipikal dimana ia akan sepenuhnya mengkonfigurasi kacang yang berkolaborasi sebelum menyuntikkannya. Misalnya, jika Bean A memiliki ketergantungan pada Bean B dan Bean B pada Bean C, penampung akan menginisialisasi C sepenuhnya sebelum menyuntikkannya ke B dan setelah B sepenuhnya diinisialisasi, wadah itu disuntikkan ke A. Tetapi dalam kasus ketergantungan melingkar, satu kacang disuntikkan ke yang lain sebelum diinisialisasi sepenuhnya.

Saurav S.
sumber
5

Katakanlah A bergantung pada B, lalu Spring pertama-tama akan membuat instance A, lalu B, lalu mengatur properti untuk B, lalu mengatur B menjadi A.

Tetapi bagaimana jika B juga bergantung pada A?

Pemahaman saya adalah: Spring baru saja menemukan bahwa A telah dibangun (konstruktor dijalankan), tetapi belum sepenuhnya diinisialisasi (tidak semua injeksi dilakukan), yah, menurutnya, tidak apa-apa, lumayanlah bahwa A tidak sepenuhnya diinisialisasi, cukup setel ini tidak- instans A yang sepenuhnya diinisialisasi ke B untuk saat ini. Setelah B sepenuhnya diinisialisasi, itu ditetapkan menjadi A, dan akhirnya, A sepenuhnya dimulai sekarang.

Dengan kata lain, ini hanya mengekspos A ke B terlebih dahulu.

Untuk dependensi melalui konstruktor, Sprint hanya membuang BeanCurrentlyInCreationException, untuk menyelesaikan pengecualian ini, setel lazy-init ke true untuk kacang yang bergantung pada orang lain melalui cara konstruktor-arg.

Diam
sumber
sederhana dan salah satu penjelasan terbaik.
Sritam Jagadev
5

Ini dijelaskan dengan jelas di sini . Terima kasih kepada Eugen Paraschiv.

Ketergantungan melingkar adalah bau desain, baik perbaiki atau gunakan @Lazy untuk ketergantungan yang menyebabkan masalah untuk mengatasinya.

siapa saya
sumber
3

Jika Anda umumnya menggunakan injeksi konstruktor dan tidak ingin beralih ke injeksi properti, maka injeksi metode pencarian Spring akan membiarkan satu kacang dengan malas mencari kacang lainnya dan karenanya mengatasi ketergantungan siklik. Lihat di sini: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161

barclar
sumber
3

Injeksi Pembuat gagal ketika ada Ketergantungan Melingkar antara kacang pegas. Jadi dalam hal ini injeksi Setter kami membantu menyelesaikan masalah.

Pada dasarnya, Constructor Injection berguna untuk dependensi Wajib, untuk dependensi opsional lebih baik menggunakan injeksi Setter karena kita bisa melakukan injeksi ulang.

Premraj
sumber
0

Jika dua biji bergantung satu sama lain maka kita tidak boleh menggunakan injeksi Pembuat di kedua definisi biji. Sebagai gantinya kita harus menggunakan injeksi penyetel di salah satu biji. (tentu saja kita dapat menggunakan injeksi penyetel pada kedua definisi kacang, tetapi injeksi konstruktor pada keduanya menampilkan 'BeanCurrentlyInCreationException'

Lihat dokumen Spring di " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource "

Srikant M
sumber