Menyuntik Kacang Prototaip ke dalam Contoh Singleton pada Musim Bunga

1. Gambaran keseluruhan

Dalam artikel ringkas ini, kita akan menunjukkan pendekatan yang berbeza untuk menyuntikkan biji prototaip ke dalam contoh tunggal . Kami akan membincangkan kes penggunaan dan kelebihan / kekurangan setiap senario.

Secara lalai, kacang musim bunga adalah singlet. Masalahnya timbul ketika kita mencuba memasang kabel dari pelbagai bidang. Contohnya, kacang prototaip menjadi singleton. Ini dikenali sebagai masalah suntikan kacang scoped .

Untuk mengetahui lebih lanjut mengenai bidang kacang, penulisan ini adalah tempat yang baik untuk memulakan.

2. Masalah Suntikan Kacang Prototaip

Untuk menerangkan masalahnya, mari konfigurasikan kacang berikut:

@Configuration public class AppConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public PrototypeBean prototypeBean() { return new PrototypeBean(); } @Bean public SingletonBean singletonBean() { return new SingletonBean(); } }

Perhatikan bahawa kacang pertama mempunyai skop prototaip, yang lain adalah singleton.

Sekarang, mari kita masukkan kacang prototaip ke dalam singleton - dan kemudian dedahkan jika melalui kaedah getPrototypeBean () :

public class SingletonBean { // .. @Autowired private PrototypeBean prototypeBean; public SingletonBean() { logger.info("Singleton instance created"); } public PrototypeBean getPrototypeBean() { logger.info(String.valueOf(LocalTime.now())); return prototypeBean; } }

Kemudian, mari muatkan ApplicationContext dan dapatkan kacang tunggal dua kali:

public static void main(String[] args) throws InterruptedException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); SingletonBean firstSingleton = context.getBean(SingletonBean.class); PrototypeBean firstPrototype = firstSingleton.getPrototypeBean(); // get singleton bean instance one more time SingletonBean secondSingleton = context.getBean(SingletonBean.class); PrototypeBean secondPrototype = secondSingleton.getPrototypeBean(); isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned"); }

Inilah keluaran dari konsol:

Singleton Bean created Prototype Bean created 11:06:57.894 // should create another prototype bean instance here 11:06:58.895

Kedua-dua kacang diinisialisasi hanya sekali, pada permulaan konteks aplikasi.

3. Suntikan ApplicationContext

Kita juga boleh memasukkan ApplicationContext terus ke dalam kacang.

Untuk mencapainya, gunakan anotasi @Autowire atau laksanakan antara muka ApplicationContextAware :

public class SingletonAppContextBean implements ApplicationContextAware { private ApplicationContext applicationContext; public PrototypeBean getPrototypeBean() { return applicationContext.getBean(PrototypeBean.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }

Setiap kali kaedah getPrototypeBean () dipanggil, contoh baru PrototypeBean akan dikembalikan dari ApplicationContext .

Walau bagaimanapun, pendekatan ini mempunyai kelemahan yang serius. Ini bertentangan dengan prinsip pembalikan kawalan, kerana kami meminta pergantungan dari kontena secara langsung.

Juga, kami mengambil prototaip kacang dari applicationContext dalam kelas SingletonAppcontextBean . Ini bermaksud menggabungkan kod ke Spring Framework .

4. Kaedah Suntikan

Kaedah lain untuk menyelesaikan masalah adalah kaedah suntikan dengan anotasi @Lookup :

@Component public class SingletonLookupBean { @Lookup public PrototypeBean getPrototypeBean() { return null; } }

Spring akan mengatasi kaedah getPrototypeBean () yang dianotasikan dengan @Lookup. Ia kemudian memasukkan kacang ke dalam konteks aplikasi. Setiap kali kita meminta kaedah getPrototypeBean () , ia mengembalikan contoh PrototypeBean baru .

Ia akan menggunakan CGLIB untuk menghasilkan bytecode yang bertanggungjawab untuk mengambil PrototypeBean dari konteks aplikasi.

5. API javax.inject

Penyediaan bersama dengan pergantungan yang diperlukan dijelaskan dalam artikel pendawaian Spring ini.

Inilah kacang tunggal:

public class SingletonProviderBean { @Autowired private Provider myPrototypeBeanProvider; public PrototypeBean getPrototypeInstance() { return myPrototypeBeanProvider.get(); } }

Kami menggunakan antara muka Penyedia untuk menyuntikkan prototaip kacang. Untuk setiap panggilan kaedah getPrototypeInstance () , myPrototypeBeanProvider. Kaedah g et () mengembalikan contoh baru PrototypeBean .

6. Proksi Skop

Secara lalai, Spring merujuk kepada objek sebenar untuk melakukan suntikan. Di sini, kami membuat objek proksi untuk mengikat objek sebenar dengan yang bergantung.

Setiap kali kaedah pada objek proksi dipanggil, proksi memutuskan sendiri sama ada untuk membuat contoh baru dari objek sebenar atau menggunakan semula yang ada.

Untuk menyiapkannya, kami mengubah kelas Appconfig untuk menambahkan anotasi @Scope baru :

@Scope( value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)

Secara lalai, Spring menggunakan pustaka CGLIB untuk mengelaskan objek secara langsung. Untuk mengelakkan penggunaan CGLIB, kita dapat mengkonfigurasi mod proksi dengan ScopedProxyMode. GANGGUAN, untuk menggunakan proksi dinamik JDK sebagai gantinya.

7. Antaramuka ObjekFactory

Spring menyediakan antara muka ObjectFactory untuk menghasilkan objek berdasarkan jenis yang diminta:

public class SingletonObjectFactoryBean { @Autowired private ObjectFactory prototypeBeanObjectFactory; public PrototypeBean getPrototypeInstance() { return prototypeBeanObjectFactory.getObject(); } }

Mari lihat kaedah getPrototypeInstance () ; getObject () mengembalikan contoh PrototypeBean yang baru untuk setiap permintaan. Di sini, kita mempunyai lebih banyak kawalan terhadap inisialisasi prototaip.

Juga, ObjectFactory sebahagian daripada rangka kerja; ini bermaksud mengelakkan penyediaan tambahan untuk menggunakan pilihan ini.

8. Create a Bean at Runtime Using java.util.Function

Another option is to create the prototype bean instances at runtime, which also allows us to add parameters to the instances.

To see an example of this, let's add a name field to our PrototypeBean class:

public class PrototypeBean { private String name; public PrototypeBean(String name) { this.name = name; logger.info("Prototype instance " + name + " created"); } //... }

Next, we'll inject a bean factory into our singleton bean by making use of the java.util.Function interface:

public class SingletonFunctionBean { @Autowired private Function beanFactory; public PrototypeBean getPrototypeInstance(String name) { PrototypeBean bean = beanFactory.apply(name); return bean; } }

Finally, we have to define the factory bean, prototype and singleton beans in our configuration:

@Configuration public class AppConfig { @Bean public Function beanFactory() { return name -> prototypeBeanWithParam(name); } @Bean @Scope(value = "prototype") public PrototypeBean prototypeBeanWithParam(String name) { return new PrototypeBean(name); } @Bean public SingletonFunctionBean singletonFunctionBean() { return new SingletonFunctionBean(); } //... }

9. Testing

Let's now write a simple JUnit test to exercise the case with ObjectFactory interface:

@Test public void givenPrototypeInjection_WhenObjectFactory_ThenNewInstanceReturn() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); SingletonObjectFactoryBean firstContext = context.getBean(SingletonObjectFactoryBean.class); SingletonObjectFactoryBean secondContext = context.getBean(SingletonObjectFactoryBean.class); PrototypeBean firstInstance = firstContext.getPrototypeInstance(); PrototypeBean secondInstance = secondContext.getPrototypeInstance(); assertTrue("New instance expected", firstInstance != secondInstance); }

Setelah berjaya melancarkan ujian, kita dapat melihat bahawa setiap kali kaedah getPrototypeInstance () dipanggil, contoh kacang prototaip baru dibuat.

10. Kesimpulannya

Dalam tutorial ringkas ini, kami mempelajari beberapa cara untuk memasukkan kacang prototaip ke dalam contoh tunggal.

Seperti biasa, kod lengkap untuk tutorial ini boleh didapati di projek GitHub.