Pengantar Pembalikan Kontrol dan Injeksi Ketergantungan dengan Pegas

1. Ikhtisar

Pada artikel ini, kami akan memperkenalkan konsep IoC (Inversion of Control) dan DI (Dependency Injection), dan kemudian kami akan melihat bagaimana ini diterapkan dalam kerangka kerja Spring.

2. Apa Itu Inversi Kontrol?

Inversion of Control adalah prinsip dalam rekayasa perangkat lunak di mana kontrol objek atau bagian dari program ditransfer ke wadah atau kerangka kerja. Ini paling sering digunakan dalam konteks pemrograman berorientasi objek.

Berbeda dengan pemrograman tradisional, di mana kode kustom kami melakukan panggilan ke perpustakaan, IoC memungkinkan kerangka kerja untuk mengontrol aliran program dan melakukan panggilan ke kode kustom kami. Untuk mengaktifkan ini, kerangka kerja menggunakan abstraksi dengan perilaku tambahan bawaan. Jika kita ingin menambahkan perilaku kita sendiri, kita perlu memperluas kelas kerangka kerja atau plugin kelas kita sendiri.

Keunggulan dari arsitektur ini adalah:

  • memisahkan pelaksanaan tugas dari implementasinya
  • sehingga lebih mudah untuk beralih di antara implementasi yang berbeda
  • modularitas yang lebih besar dari sebuah program
  • kemudahan yang lebih besar dalam menguji program dengan mengisolasi komponen atau meniru dependensinya dan memungkinkan komponen berkomunikasi melalui kontrak

Inversion of Control dapat dicapai melalui berbagai mekanisme seperti: Pola desain strategi, pola Service Locator, pola Pabrik, dan Dependency Injection (DI).

Kita akan melihat DI selanjutnya.

3. Apa itu Injeksi Ketergantungan?

Injeksi ketergantungan adalah pola yang digunakan untuk mengimplementasikan IoC, di mana kontrol yang dibalik adalah pengaturan ketergantungan objek.

Tindakan menghubungkan objek dengan objek lain, atau "memasukkan" objek ke objek lain, dilakukan oleh assembler daripada oleh objek itu sendiri.

Inilah cara Anda membuat ketergantungan objek dalam pemrograman tradisional:

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

Dalam contoh di atas, kita perlu membuat contoh implementasi antarmuka Item dalam kelas Store itu sendiri.

Dengan menggunakan DI, kita dapat menulis ulang contoh tanpa menentukan implementasi Item yang kita inginkan:

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

Di bagian selanjutnya, kita akan melihat bagaimana kita dapat menyediakan implementasi Item melalui metadata.

Baik IoC dan DI adalah konsep sederhana, tetapi memiliki implikasi yang mendalam dalam cara kami menyusun sistem kami, jadi keduanya layak dipahami dengan baik.

4. Wadah IoC Musim Semi

Wadah IoC adalah karakteristik umum kerangka kerja yang mengimplementasikan IoC.

Dalam framework Spring, container IoC diwakili oleh antarmuka ApplicationContext . Wadah Spring bertanggung jawab untuk membuat instance, mengonfigurasi, dan merakit objek yang dikenal sebagai kacang , serta mengelola siklus hidupnya.

Framework Spring menyediakan beberapa implementasi antarmuka ApplicationContext - ClassPathXmlApplicationContext dan FileSystemXmlApplicationContext untuk aplikasi mandiri, dan WebApplicationContext untuk aplikasi web.

Untuk merakit bean, container menggunakan konfigurasi metadata, yang bisa dalam bentuk konfigurasi XML atau anotasi.

Berikut salah satu cara untuk membuat instance container secara manual:

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

Untuk mengatur atribut item pada contoh di atas, kita dapat menggunakan metadata. Kemudian, container akan membaca metadata ini dan menggunakannya untuk mengumpulkan kacang pada waktu proses.

Dependency Injection di Spring dapat dilakukan melalui konstruktor, setter atau field.

5. Injeksi Ketergantungan Berbasis Konstruktor

Dalam kasus injeksi ketergantungan berbasis konstruktor, penampung akan memanggil konstruktor dengan argumen masing-masing mewakili ketergantungan yang ingin kita setel.

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

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

The @Configuration penjelasan menunjukkan bahwa kelas merupakan sumber definisi kacang. Juga, kita dapat menambahkannya ke beberapa kelas konfigurasi.

The @Bean penjelasan digunakan pada metode untuk menentukan kacang. Jika kami tidak menentukan nama kustom, nama kacang akan default ke nama metode.

Untuk kacang dengan lingkup tunggal default , Spring pertama memeriksa apakah turunan yang di-cache dari kacang sudah ada dan hanya membuat yang baru jika tidak. Jika kita menggunakan lingkup prototipe , wadah mengembalikan contoh kacang baru untuk setiap pemanggilan metode.

Cara lain untuk membuat konfigurasi kacang adalah melalui konfigurasi XML:

6. Injeksi Ketergantungan Berbasis Setter

Untuk DI berbasis setter, kontainer akan memanggil metode penyetel kelas kita, setelah memanggil konstruktor tanpa argumen atau metode pabrik statis tanpa argumen untuk membuat instance kacang. Mari buat konfigurasi ini menggunakan anotasi:

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

Kita juga bisa 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 Kontainer Kontrol dan pola Injeksi Ketergantungan.
  • Pembalikan Kontrol

Dan Anda dapat mempelajari lebih lanjut tentang implementasi Spring dari IoC dan DI di Spring Framework Reference Documentation.