Pengenalan Inversi Suntikan Kawalan dan Ketergantungan dengan Musim Semi

1. Gambaran keseluruhan

Dalam artikel ini, kami akan memperkenalkan konsep IoC (Inversion of Control) dan DI (Dependency Injection), dan kemudian kami akan melihat bagaimana ini dilaksanakan dalam rangka Spring.

2. Apakah Pembalikan Kawalan?

Inversion of Control adalah prinsip dalam kejuruteraan perisian di mana kawalan objek atau bahagian program dipindahkan ke wadah atau kerangka kerja. Ia paling sering digunakan dalam konteks pengaturcaraan berorientasikan objek.

Berbeza dengan pengaturcaraan tradisional, di mana kod khusus kami membuat panggilan ke perpustakaan, IoC memungkinkan kerangka kerja untuk mengawal aliran program dan membuat panggilan ke kod khusus kami. Untuk mengaktifkannya, kerangka kerja menggunakan abstraksi dengan tingkah laku tambahan yang terbina dalam. Sekiranya kita mahu menambahkan tingkah laku kita sendiri, kita perlu memperluas kelas kerangka atau memasukkan kelas kita sendiri.

Kelebihan seni bina ini adalah:

  • memisahkan pelaksanaan tugas dari pelaksanaannya
  • menjadikannya lebih mudah untuk beralih antara pelaksanaan yang berbeza
  • modulariti program yang lebih besar
  • kemudahan yang lebih besar dalam menguji program dengan mengasingkan komponen atau mengejek pergantungannya dan membiarkan komponen berkomunikasi melalui kontrak

Inversi Kawalan dapat dicapai melalui pelbagai mekanisme seperti: Corak reka bentuk strategi, corak Service Locator, corak kilang, dan Dependency Injection (DI).

Kita akan melihat DI seterusnya.

3. Apa itu Suntikan Ketergantungan?

Suntikan ketergantungan adalah corak untuk melaksanakan IoC, di mana kawalan yang terbalik adalah pengaturan kebergantungan objek.

Tindakan menghubungkan objek dengan objek lain, atau "menyuntikkan" objek ke objek lain, dilakukan oleh pemasang dan bukan oleh objek itu sendiri.

Inilah cara anda membuat ketergantungan objek dalam pengaturcaraan tradisional:

public class Store { private Item item; public Store() { item = new ItemImpl1(); } }

Dalam contoh di atas, kita perlu memberi contoh pelaksanaan antara muka Item dalam kelas Store itu sendiri.

Dengan menggunakan DI, kita dapat menulis semula contohnya tanpa menentukan pelaksanaan Item yang kita mahukan:

public class Store { private Item item; public Store(Item item) { this.item = item; } }

Di bahagian seterusnya, kita akan melihat bagaimana kita dapat menyediakan pelaksanaan Item melalui metadata.

Kedua-dua IoC dan DI adalah konsep yang mudah, tetapi mempunyai implikasi yang mendalam dalam cara kita menyusun sistem kita, jadi mereka patut difahami dengan baik.

4. Bekas Spring IoC

Wadah IoC adalah ciri umum kerangka kerja yang melaksanakan IoC.

Dalam rangka Spring, kontena IoC diwakili oleh antara muka ApplicationContext . Bekas Spring bertanggungjawab untuk membuat, mengkonfigurasi dan memasang objek yang dikenali sebagai kacang , serta menguruskan kitaran hidupnya.

Rangka kerja Spring menyediakan beberapa pelaksanaan daripada ApplicationContext antara muka - ClassPathXmlApplicationContext dan FileSystemXmlApplicationContext untuk aplikasi berdiri sendiri, dan WebApplicationContext untuk aplikasi web.

Untuk memasang kacang, wadah menggunakan metadata konfigurasi, yang boleh berupa konfigurasi atau anotasi XML.

Inilah salah satu kaedah untuk membuat bekas secara manual:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

Untuk menetapkan atribut item dalam contoh di atas, kita dapat menggunakan metadata. Kemudian, bekas akan membaca metadata ini dan menggunakannya untuk menyusun kacang pada waktu proses.

Suntikan Ketergantungan pada Musim Bunga boleh dilakukan melalui konstruktor, setter atau ladang.

5. Suntikan Ketergantungan Berasaskan Konstruktor

Dalam kes suntikan kebergantungan berasaskan konstruktor, bekas akan memanggil konstruktor dengan argumen yang masing-masing mewakili kebergantungan yang ingin kita tetapkan.

Spring menyelesaikan setiap argumen terutamanya berdasarkan jenis, diikuti dengan nama atribut dan indeks untuk disambiguasi. Mari lihat konfigurasi kacang dan pergantungannya menggunakan anotasi:

@Configuration public class AppConfig { @Bean public Item item1() { return new ItemImpl1(); } @Bean public Store store() { return new Store(item1()); } }

The @Configuration anotasi menunjukkan bahawa kelas adalah sumber definisi kacang. Kita juga boleh menambahkannya ke beberapa kelas konfigurasi.

The @Bean anotasi digunakan pada kaedah untuk menentukan sebiji kacang. Sekiranya kita tidak menentukan nama khusus, nama kacang akan lalai ke nama kaedah.

Untuk kacang dengan skop singleton lalai , Spring pertama memeriksa apakah contoh kacang cache sudah ada dan hanya membuat yang baru jika tidak. Sekiranya kita menggunakan skop prototaip , bekas mengembalikan contoh kacang baru untuk setiap panggilan kaedah.

Cara lain untuk membuat konfigurasi kacang adalah melalui konfigurasi XML:

6. Suntikan Ketergantungan Berasaskan Setter

Untuk DI berasaskan setter, kontena akan memanggil kaedah setter kelas kami, setelah meminta kaedah kilang tanpa argumen atau kaedah kilang statik tanpa argumen untuk mewujudkan kacang. Mari buat konfigurasi ini menggunakan anotasi:

@Bean public Store store() { Store store = new Store(); store.setItem(item1()); return store; }

Kami juga boleh menggunakan XML untuk konfigurasi kacang yang sama:

Constructor-based and setter-based types of injection can be combined for the same bean. The Spring documentation recommends using constructor-based injection for mandatory dependencies, and setter-based injection for optional ones.

7. Field-Based Dependency Injection

In case of Field-Based DI, we can inject the dependencies by marking them with an @Autowired annotation:

public class Store { @Autowired private Item item; }

While constructing the Store object, if there's no constructor or setter method to inject the Item bean, the container will use reflection to inject Item into Store.

We can also achieve this using XML configuration.

This approach might look simpler and cleaner but is not recommended to use because it has a few drawbacks such as:

  • This method uses reflection to inject the dependencies, which is costlier than constructor-based or setter-based injection
  • It's really easy to keep adding multiple dependencies using this approach. If you were using constructor injection having multiple arguments would have made us think that the class does more than one thing which can violate the Single Responsibility Principle.

More information on @Autowired annotation can be found in Wiring In Spring article.

8. Autowiring Dependencies

Wiring allows the Spring container to automatically resolve dependencies between collaborating beans by inspecting the beans that have been defined.

There are four modes of autowiring a bean using an XML configuration:

  • no: the default value – this means no autowiring is used for the bean and we have to explicitly name the dependencies
  • byName: autowiring is done based on the name of the property, therefore Spring will look for a bean with the same name as the property that needs to be set
  • byType: similar to the byName autowiring, only based on the type of the property. This means Spring will look for a bean with the same type of the property to set. If there's more than one bean of that type, the framework throws an exception.
  • constructor: autowiring is done based on constructor arguments, meaning Spring will look for beans with the same type as the constructor arguments

For example, let's autowire the item1 bean defined above by type into the store bean:

@Bean(autowire = Autowire.BY_TYPE) public class Store { private Item item; public setItem(Item item){ this.item = item; } }

We can also inject beans using the @Autowired annotation for autowiring by type:

public class Store { @Autowired private Item item; }

If there's more than one bean of the same type, we can use the @Qualifier annotation to reference a bean by name:

public class Store { @Autowired @Qualifier("item1") private Item item; }

Now, let's autowire beans by type through XML configuration:

Next, let's inject a bean named item into the item property of store bean by name through XML:

We can also override the autowiring by defining dependencies explicitly through constructor arguments or setters.

9. Lazy Initialized Beans

By default, the container creates and configures all singleton beans during initialization. To avoid this, you can use the lazy-init attribute with value true on the bean configuration:

As a consequence, the item1 bean will be initialized only when it's first requested, and not at startup. The advantage of this is faster initialization time, but the trade-off is that configuration errors may be discovered only after the bean is requested, which could be several hours or even days after the application has already been running.

10. Conclusion

In this article, we've presented the concepts of inversion of control and dependency injection and exemplified them in the Spring framework.

You can read more about these concepts in Martin Fowler's articles:

  • Pembalikan Kontena Kontena dan corak Suntikan Ketergantungan.
  • Pembalikan Kawalan

Dan anda boleh mengetahui lebih lanjut mengenai pelaksanaan Spring dari IoC dan DI dalam Spring Framework Reference Documentation.