Ketergantungan Pekeliling pada Musim Bunga

1. Apa itu Ketergantungan Pekeliling?

Ia berlaku apabila kacang A bergantung pada kacang B yang lain, dan kacang B bergantung pada kacang A juga:

Kacang A → Kacang B → Kacang A

Sudah tentu, kita boleh mempunyai lebih banyak kacang yang tersirat:

Kacang A → Kacang B → Kacang C → Kacang D → Kacang E → Kacang A

2. Apa yang Berlaku pada Musim Bunga

Semasa konteks Spring memuatkan semua biji, ia cuba membuat kacang mengikut urutan yang diperlukan agar ia berfungsi sepenuhnya. Contohnya, jika kita tidak memiliki ketergantungan melingkar, seperti kes berikut:

Kacang A → Kacang B → Kacang C

Musim bunga akan membuat kacang C, kemudian membuat kacang B (dan menyuntik kacang C ke dalamnya), kemudian membuat kacang A (dan menyuntik kacang B ke dalamnya).

Tetapi, ketika mempunyai ketergantungan bulat, Spring tidak dapat memutuskan biji mana yang harus dibuat terlebih dahulu, kerana mereka bergantung satu sama lain. Dalam kes ini, Spring akan menaikkan BeanCurrentlyInCreationException semasa memuatkan konteks.

Ia boleh berlaku pada musim bunga semasa menggunakan suntikan konstruktor ; jika anda menggunakan suntikan jenis lain, anda seharusnya tidak menemui masalah ini kerana kebergantungan akan disuntik apabila diperlukan dan bukan pada pemuatan konteks.

3. Contoh Pantas

Mari kita tentukan dua kacang yang saling bergantung (melalui suntikan konstruktor):

@Component public class CircularDependencyA { private CircularDependencyB circB; @Autowired public CircularDependencyA(CircularDependencyB circB) { this.circB = circB; } }
@Component public class CircularDependencyB { private CircularDependencyA circA; @Autowired public CircularDependencyB(CircularDependencyA circA) { this.circA = circA; } }

Sekarang kita dapat menulis kelas Konfigurasi untuk ujian, mari kita menyebutnya TestConfig , yang menentukan paket dasar untuk mengimbas komponen. Mari kita anggap kacang kita ditentukan dalam pakej " com.baeldung.circulardependency ":

@Configuration @ComponentScan(basePackages = { "com.baeldung.circulardependency" }) public class TestConfig { }

Dan akhirnya kita dapat menulis ujian JUnit untuk memeriksa kebergantungan pekeliling. Ujian boleh kosong, kerana ketergantungan bulat akan dikesan semasa pemuatan konteks.

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { TestConfig.class }) public class CircularDependencyTest { @Test public void givenCircularDependency_whenConstructorInjection_thenItFails() { // Empty test; we just want the context to load } }

Sekiranya anda cuba menjalankan ujian ini, anda akan mendapat pengecualian berikut:

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?

4. Penyelesaiannya

Kami akan menunjukkan beberapa kaedah yang paling popular untuk mengatasi masalah ini.

4.1. Merangka semula

Apabila anda mempunyai ketergantungan bulat, kemungkinan anda mempunyai masalah reka bentuk dan tanggungjawabnya tidak dipisahkan dengan baik. Anda harus mencuba merancang semula komponen dengan betul supaya hirarki mereka dirancang dengan baik dan tidak memerlukan pergantungan bulat.

Sekiranya anda tidak dapat merancang semula komponen (ada banyak kemungkinan sebab untuk itu: kod warisan, kod yang telah diuji dan tidak dapat diubah, tidak cukup masa atau sumber untuk reka bentuk semula lengkap ...), ada beberapa jalan penyelesaian untuk dicuba.

4.2. Gunakan @Lazy

Kaedah mudah untuk mematahkan kitaran adalah mengatakan Spring untuk memulakan salah satu kacang dengan malas. Iaitu: alih-alih menginisialkan kacang sepenuhnya, ia akan membuat proksi untuk memasukkannya ke dalam kacang yang lain. Kacang yang disuntik hanya akan dibuat sepenuhnya ketika pertama kali diperlukan.

Untuk mencuba ini dengan kod kami, anda boleh mengubah CircularDependencyA menjadi berikut:

@Component public class CircularDependencyA { private CircularDependencyB circB; @Autowired public CircularDependencyA(@Lazy CircularDependencyB circB) { this.circB = circB; } }

Sekiranya anda menjalankan ujian sekarang, anda akan melihat bahawa kesalahan itu tidak berlaku kali ini.

4.3. Gunakan Suntikan Setter / Medan

Salah satu penyelesaian yang paling popular, dan juga apa yang dicadangkan oleh dokumentasi Spring, adalah menggunakan suntikan setter.

Ringkasnya jika anda mengubah cara kacang anda menggunakan suntikan setter (atau suntikan ladang) dan bukannya suntikan konstruktor - yang mengatasi masalah ini. Dengan cara ini Musim Bunga menghasilkan kacang, tetapi kebergantungan tidak disuntik sehingga diperlukan.

Mari lakukan itu - mari ubah kelas kami untuk menggunakan suntikan setter dan akan menambahkan medan lain ( mesej ) ke CircularDependencyB supaya kami dapat membuat ujian unit yang betul:

@Component public class CircularDependencyA { private CircularDependencyB circB; @Autowired public void setCircB(CircularDependencyB circB) { this.circB = circB; } public CircularDependencyB getCircB() { return circB; } }
@Component public class CircularDependencyB { private CircularDependencyA circA; private String message = "Hi!"; @Autowired public void setCircA(CircularDependencyA circA) { this.circA = circA; } public String getMessage() { return message; } }

Sekarang kita harus membuat beberapa perubahan pada ujian unit kita:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { TestConfig.class }) public class CircularDependencyTest { @Autowired ApplicationContext context; @Bean public CircularDependencyA getCircularDependencyA() { return new CircularDependencyA(); } @Bean public CircularDependencyB getCircularDependencyB() { return new CircularDependencyB(); } @Test public void givenCircularDependency_whenSetterInjection_thenItWorks() { CircularDependencyA circA = context.getBean(CircularDependencyA.class); Assert.assertEquals("Hi!", circA.getCircB().getMessage()); } }

Berikut menerangkan anotasi yang dilihat di atas:

@Bean : Untuk memberitahu kerangka Spring bahawa kaedah ini mesti digunakan untuk mengambil pelaksanaan kacang untuk disuntik.

@Test : Ujian ini akan mendapatkan kacang CircularDependencyA dari konteks dan menegaskan bahawa CircularDependencyB yang telah disuntik dengan betul, check-nilai yang mesej harta.

4.4. Gunakan @PostConstruct

Cara lain untuk memecahkan kitaran adalah menyuntikkan kebergantungan menggunakan @Autowired pada salah satu kacang, dan kemudian gunakan kaedah yang diberi penjelasan dengan @PostConstruct untuk menetapkan kebergantungan yang lain.

Kacang kami mempunyai kod berikut:

@Component public class CircularDependencyA { @Autowired private CircularDependencyB circB; @PostConstruct public void init() { circB.setCircA(this); } public CircularDependencyB getCircB() { return circB; } }
@Component public class CircularDependencyB { private CircularDependencyA circA; private String message = "Hi!"; public void setCircA(CircularDependencyA circA) { this.circA = circA; } public String getMessage() { return message; } }

And we can run the same test we previously had, so we check that the circular dependency exception is still not being thrown and that the dependencies are properly injected.

4.5. Implement ApplicationContextAware and InitializingBean

If one of the beans implements ApplicationContextAware, the bean has access to Spring context and can extract the other bean from there. Implementing InitializingBean we indicate that this bean has to do some actions after all its properties have been set; in this case, we want to manually set our dependency.

The code of our beans would be:

@Component public class CircularDependencyA implements ApplicationContextAware, InitializingBean { private CircularDependencyB circB; private ApplicationContext context; public CircularDependencyB getCircB() { return circB; } @Override public void afterPropertiesSet() throws Exception { circB = context.getBean(CircularDependencyB.class); } @Override public void setApplicationContext(final ApplicationContext ctx) throws BeansException { context = ctx; } }
@Component public class CircularDependencyB { private CircularDependencyA circA; private String message = "Hi!"; @Autowired public void setCircA(CircularDependencyA circA) { this.circA = circA; } public String getMessage() { return message; } }

Again, we can run the previous test and see that the exception is not thrown and that the test is working as expected.

5. In Conclusion

Terdapat banyak cara untuk menangani pergantungan pekeliling pada musim bunga. Perkara pertama yang perlu dipertimbangkan adalah merancang semula kacang anda supaya tidak memerlukan pergantungan bulat: biasanya merupakan gejala reka bentuk yang dapat diperbaiki.

Tetapi jika anda benar-benar memerlukan pergantungan bulat dalam projek anda, anda boleh mengikuti beberapa kaedah penyelesaian yang disarankan di sini.

Kaedah yang disukai adalah menggunakan suntikan setter. Tetapi ada alternatif lain, umumnya berdasarkan pada menghentikan Spring daripada mengatur inisialisasi dan suntikan kacang, dan melakukannya sendiri menggunakan satu strategi atau strategi lain.

Contohnya boleh didapati dalam projek GitHub.