Mendapatkan Spring Bean dari Konteks Aplikasi pihak ketiga dengan benar

Selamat siang, orang Khabrovit!

Dalam artikel ini, saya mengusulkan untuk membahas salah satu masalah yang sering dihadapi dalam proyek-proyek menggunakan kerangka kerja Spring. Masalah yang dijelaskan dalam artikel ini muncul karena salah satu kesalahan khas dalam konfigurasi pegas. Tidak perlu mencoba membuat kesalahan dalam konfigurasi, dan karena itu kesalahan ini cukup umum.

Pernyataan masalah


Masalah yang disajikan dalam artikel ini terkait dengan konfigurasi kacang yang salah dalam konteks aplikasi saat ini, yang diambil dari konteks aplikasi lainnya. Masalah seperti itu dapat muncul dalam aplikasi industri besar, yang terdiri dari banyak toples, yang masing-masing memiliki konteks aplikasi sendiri yang mengandung kacang panjang.

Sebagai hasil dari konfigurasi yang salah, kami mendapatkan beberapa salinan kacang dengan keadaan yang tidak dapat diprediksi, bahkan jika mereka memiliki cakupan tunggal. Selain itu, penyalinan kacang secara sembarangan dapat menyebabkan fakta bahwa lebih dari selusin kopi dari semua biji kopi akan dibuat dalam aplikasi, yang penuh dengan masalah kinerja aplikasi dan peningkatan waktu startup aplikasi.

Contoh menggunakan kacang dari konteks aplikasi eksternal saat ini


Bayangkan kita sedang mengembangkan di salah satu modul aplikasi, di mana ada banyak modul lain, dan masing-masing modul memiliki konteks aplikasi sendiri. Aplikasi semacam itu harus memiliki modul tempat instance konteks aplikasi dari semua modul aplikasi dibuat.



Misalkan, dalam konteks aplikasi salah satu modul eksternal, sebuah instance dari kacang kelas NumberGenerator dibuat, yang ingin kita dapatkan di modul kami. Anggap juga bahwa kelas NumberGenerator terletak di paket org.example.kruchon.generators, yang menyimpan beberapa kelas yang menghasilkan nilai.



Kacang ini memiliki status - bidang hitungan int.

package org.example.kruchon.calculators public class NumberGenerator { private int count = 0; public synchronized int next() { return count++; } } 

Sebuah instance dari kacang ini dibuat dalam subkonfigurasi GeneratorsConfiguration.

 @Configuration public class GeneratorsConfiguration { @Bean public NumberGenerator numberGenerator() { return new NumberGenerator(); } ... } 

Juga dalam konteks aplikasi eksternal, ada konfigurasi utama di mana semua subkonfigurasi modul eksternal diimpor.

 @Configuration @Import({GeneratorsConfiguration.class, ...}) public class ExternalContextConfiguration { ... } 

Sekarang saya akan memberikan beberapa contoh di mana kacang tunggal dari kelas NumberGenerator dikonfigurasi secara tidak benar dalam konfigurasi konteks aplikasi saat ini.

Konfigurasi yang salah 1. Mengimpor konfigurasi utama dari konteks aplikasi eksternal


Keputusan terburuk yang bisa diambil.

 @Configuration @Import(ExternalContextConfiguration.class) public class CurrentContextConfiguration { ... } 

  • Aplikasi ini menciptakan kembali semua contoh kacang dari konteks aplikasi eksternal. Dengan kata lain, salinan seluruh modul eksternal dibuat, yang memengaruhi konsumsi memori, kinerja, dan waktu startup aplikasi.
  • Dapatkan salinan NumberGenerator dalam konteks aplikasi saat ini. Salinan NumberGenerator memiliki nilai sendiri untuk bidang hitung, tidak konsisten dengan instance pertama NumberGenerator. Ketidakkonsistenan ini memunculkan kesalahan yang sulit di-debug dalam aplikasi.

Konfigurasi yang salah 2. Impor subkonfigurasi konteks aplikasi eksternal


Pilihan kedua tidak benar dan sering ditemui dalam praktik.

 @Configuration @Import(GeneratorsConfiguration.class) public class CurrentContextConfiguration { ... } 

Dalam perwujudan ini, salinan lengkap dari modul eksternal tidak lagi dibuat, namun, kami kembali mendapatkan kacang kedua dari kelas NumberGenerator.

Konfigurasi salah 3. Cari injeksi langsung ke kacang, di mana kami ingin menggunakan NumberGenerator


 public class OrderFactory { private final NumberGenerator numberGenerator; public OrderFactory() { ApplicationContext externalApplicationContext = getExternalContext(); numberGenerator = externalApplicationContext.getBean(NumberGenerator.class); } public Order create() { Order order = new Order(); int id = numberGenerator.next(); order.setId(id); order.setCreatedDate(new Date()); return order; } private ApplicationContext getExternalContext(){ ... } } 

Dalam metode ini, duplikasi kacang memiliki lingkup tunggal dapat dianggap diselesaikan. Memang, sekarang kami menggunakan kembali kacang dari konteks aplikasi lain dan tidak membuatnya kembali!

Tapi begini:

  1. Rumit kelas yang dikembangkan dan pengujian unitnya.
  2. Tidak termasuk implementasi otomatis kacang kelas NumberGenerator dalam kacang modul saat ini.
  3. Biasanya tidak menggunakan lookUp untuk menyuntikkan kacang singleton dalam kasus umum.

Oleh karena itu, solusi seperti itu lebih seperti solusi canggung daripada solusi rasional untuk masalah.

Pertimbangkan cara mengonfigurasi kacang dengan benar dari konteks aplikasi eksternal.

Solusi 1. Dapatkan kacang dari konteks aplikasi eksternal dalam konfigurasi


Metode ini sangat mirip dengan contoh ke-3 dari konfigurasi yang salah dengan satu perbedaan: kita mendapatkan kacang dengan membuat pencarian dari konteks eksternal dalam konfigurasi, dan tidak langsung ke kacang.

 @Configuration public class CurrentContextConfiguration { @Bean public NumberGenerator numberGenerator() { ApplicationContext externalApplicationContext = getExternalContext(); return externalApplicationContext.getBean(NumberGenerator.class); } private ApplicationContext getExternalContext(){ ... } } 

Sekarang kita dapat secara otomatis menanamkan kacang ini dalam kacang dari modul kita sendiri.

Solusi 2. Jadikan konteks aplikasi eksternal sebagai induk


Kemungkinan fungsionalitas modul saat ini memperluas fungsionalitas eksternal. Mungkin ada kasus ketika di salah satu modul tambahan kacang tambahan yang umum untuk seluruh aplikasi dikembangkan, dan dalam modul lain kacang ini digunakan. Dalam hal ini, logis untuk menunjukkan bahwa modul eksternal adalah induk dari yang sebelumnya. Dalam hal ini, semua kacang dari modul induk dapat digunakan dalam modul saat ini, dan kemudian kacang modul induk tidak perlu dikonfigurasi dalam konfigurasi konteks aplikasi saat ini.

Dimungkinkan untuk menentukan hubungan induk saat membuat contoh konteks menggunakan konstruktor dengan parameter induk:

 public AbstractApplicationContext(ApplicationContext parent) { ... } 

Atau gunakan setter:

 public void setParent(ApplicationContext parent) { ... } 

Jika konteks aplikasi dideklarasikan dalam xml, kita dapat menggunakan konstruktor:

 public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { ... } 

Kesimpulan


Jadi, berhati-hatilah saat mengonfigurasi kacang panjang, ikuti rekomendasi dalam artikel dan cobalah untuk tidak menyalin kacang yang memiliki cakupan tunggal. Saya akan dengan senang hati menjawab pertanyaan Anda!

Source: https://habr.com/ru/post/id472716/


All Articles