Menggunakan Spring's Java Config, saya perlu mendapatkan / membuat contoh prototipe-scoped bean dengan argumen konstruktor yang hanya dapat diperoleh saat runtime. Pertimbangkan contoh kode berikut (disederhanakan untuk singkatnya):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
di mana kelas Thing didefinisikan sebagai berikut:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Perhatikan name
adalah final
: itu hanya dapat diberikan melalui konstruktor, dan menjamin keabadian. Ketergantungan lain adalah dependensi implementasi-spesifik dari Thing
kelas, dan tidak boleh diketahui (digabungkan erat dengan) implementasi penangan permintaan.
Kode ini berfungsi dengan baik dengan konfigurasi Spring XML, misalnya:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
Bagaimana cara mencapai hal yang sama dengan konfigurasi Java? Berikut ini tidak berfungsi menggunakan Spring 3.x:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Sekarang, saya dapat membuat Pabrik, misalnya:
public interface ThingFactory {
public Thing createThing(String name);
}
Tapi itu mengalahkan seluruh titik menggunakan Spring untuk mengganti pola desain ServiceLocator dan Factory , yang akan ideal untuk use case ini.
Jika Spring Java Config dapat melakukan ini, saya akan dapat menghindari:
- mendefinisikan antarmuka pabrik
- mendefinisikan implementasi Pabrik
- tes tertulis untuk implementasi Pabrik
Itu satu ton pekerjaan (relatif berbicara) untuk sesuatu yang sangat sepele bahwa Spring sudah mendukung melalui konfigurasi XML.
Thing
implementasi sebenarnya lebih kompleks dan memang memiliki ketergantungan pada kacang lainnya (saya hanya menghilangkannya untuk singkatnya). Karena itu, saya tidak ingin implementasi Request handler tahu tentang mereka, karena ini akan secara ketat memasangkan handler ke API / kacang yang tidak perlu. Saya akan memperbarui pertanyaan untuk mencerminkan pertanyaan Anda (luar biasa).@Qualifier
parameter ke setter dengan@Autowired
setter itu sendiri.@Bean
karya. The@Bean
Metode dipanggil dengan argumen yang tepat Anda dilewatkan kegetBean(..)
.Jawaban:
Di
@Configuration
kelas,@Bean
metode seperti itudigunakan untuk mendaftarkan definisi kacang dan menyediakan pabrik untuk membuat kacang . Kacang yang didefinisikan hanya dipakai berdasarkan permintaan menggunakan argumen yang ditentukan secara langsung atau melalui pemindaian itu
ApplicationContext
.Dalam kasus
prototype
kacang, objek baru dibuat setiap waktu dan karenanya@Bean
metode yang sesuai juga dieksekusi.Anda dapat mengambil kacang dari
ApplicationContext
melaluiBeanFactory#getBean(String name, Object... args)
metodenya yang menyatakanDengan kata lain, untuk
prototype
kacang yang dicakup ini , Anda memberikan argumen yang akan digunakan, bukan dalam konstruktor dari kelas kacang, tetapi dalam@Bean
doa metode.Setidaknya ini berlaku untuk versi Spring 4+.
sumber
@Bean
metode untuk doa manual. Jika Anda pernah@Autowire Thing
menggunakan@Bean
metode ini kemungkinan akan mati karena tidak dapat menyuntikkan parameter. Sama jika Anda@Autowire List<Thing>
. Saya menemukan ini agak rapuh.@Autowire
dengan?Dengan Spring> 4.0 dan Java 8 Anda dapat melakukan ini dengan lebih aman:
Pemakaian:
Jadi sekarang Anda bisa mendapatkan kacang Anda saat runtime. Ini adalah pola pabrik tentu saja, tetapi Anda dapat menghemat waktu untuk menulis kelas khusus seperti
ThingFactory
(namun Anda harus menulis kustom@FunctionalInterface
untuk melewati lebih dari dua parameter).sumber
fabric
bukanfactory
, buruk saya :)Provider
atau keObjectFactory
, atau apakah saya salah? Dan dalam contoh saya, Anda dapat melewatkan parameter string ke sana (atau parameter apa pun)@Bean
danScope
penjelasan atasThing thing
metode. Selain itu metode ini dapat dibuat pribadi untuk menyembunyikan dirinya sendiri dan hanya menyisakan pabrik.Sejak Musim Semi 4.3, ada cara baru untuk melakukannya, yang dijahit untuk masalah itu.
ObjectProvider - Ini memungkinkan Anda untuk menambahkannya sebagai ketergantungan pada kacang yang dicakup Prototipe "yang diperdebatkan" Anda dan untuk instantiate menggunakan argumen.
Berikut ini adalah contoh sederhana cara menggunakannya:
Ini tentu saja akan mencetak halo string ketika memanggil usePrototype.
sumber
DIPERBARUI per komentar
Pertama, saya tidak yakin mengapa Anda mengatakan "ini tidak berhasil" untuk sesuatu yang berfungsi dengan baik di Spring 3.x. Saya menduga ada sesuatu yang salah dalam konfigurasi Anda di suatu tempat.
Ini bekerja:
- File Konfigurasi:
- Uji File untuk mengeksekusi:
Menggunakan Spring 3.2.8 dan Java 7, memberikan output ini:
Jadi 'Singleton' Bean diminta dua kali. Namun seperti yang kita harapkan, Spring hanya menciptakannya sekali. Kali kedua ia melihat bahwa ia memiliki kacang itu dan hanya mengembalikan objek yang ada. Konstruktor (metode @Bean) tidak dipanggil untuk kedua kalinya. Sehubungan dengan ini, ketika 'Prototipe' Bean diminta dari objek konteks yang sama dua kali kita melihat bahwa perubahan referensi dalam output DAN bahwa konstruktor (metode @Bean) IS dipanggil dua kali.
Jadi pertanyaannya adalah bagaimana menyuntikkan singleton ke dalam prototipe. Kelas konfigurasi di atas menunjukkan cara melakukannya juga! Anda harus meneruskan semua referensi tersebut ke konstruktor. Ini akan memungkinkan kelas yang dibuat menjadi POJO murni serta membuat objek referensi yang ada tidak berubah sebagaimana mestinya. Jadi layanan transfer mungkin terlihat seperti:
Jika Anda menulis Tes Unit Anda akan sangat senang Anda membuat kelas ini tanpa semua @Autowired. Jika Anda membutuhkan komponen autowired, simpan komponen lokal itu ke file konfigurasi java.
Ini akan memanggil metode di bawah ini di BeanFactory. Perhatikan dalam deskripsi bagaimana ini dimaksudkan untuk kasus penggunaan yang tepat.
sumber
Anda dapat mencapai efek serupa hanya dengan menggunakan kelas batin :
sumber
dalam file xml kacang Anda gunakan atribut scope = "prototype"
sumber
Terlambat menjawab dengan pendekatan yang sedikit berbeda. Itu adalah tindak lanjut dari pertanyaan terbaru ini yang merujuk pertanyaan ini sendiri.
Ya, seperti yang dikatakan Anda dapat mendeklarasikan kacang prototipe yang menerima parameter di
@Configuration
kelas yang memungkinkan untuk membuat kacang baru di setiap injeksi.Itu akan membuat
@Configuration
kelas ini menjadi pabrik dan untuk tidak memberikan pabrik ini terlalu banyak tanggung jawab, ini tidak termasuk kacang lainnya.Tetapi Anda juga dapat menyuntikkan kacang konfigurasi itu untuk membuat
Thing
s:Jenisnya aman dan ringkas.
sumber
@Configuration
jika mekanisme itu berubah.BeanFactory#getBean()
. Tapi itu jauh lebih buruk dalam hal penggandaan karena itu adalah pabrik yang memungkinkan untuk mendapatkan / membuat instance kacang aplikasi dan tidak hanya yang mana yang dibutuhkan kacang saat ini. Dengan penggunaan seperti itu Anda dapat mencampur tanggung jawab kelas Anda dengan sangat mudah karena dependensi yang mungkin ditarik tidak terbatas, yang sebenarnya tidak disarankan tetapi merupakan kasus luar biasa.