Bagaimana Spring menyelesaikan ini: kacang A bergantung pada kacang B, dan kacang B pada kacang A.
Bagaimana Spring menyelesaikan ini: kacang A bergantung pada kacang B, dan kacang B pada kacang A.
Jawaban:
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.)
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.
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
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.
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 };
}
Itu hanya melakukannya. Ini membuat instance adan b, dan menyuntikkan masing-masing satu sama lain (menggunakan metode penyetelnya).
Apa masalahnya?
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.
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.
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.
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.
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
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.
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 "