Giới thiệu về Inversion of Control(IoC) và Dependency Injection(DI) trong Spring
1. Giới thiệu
Hiện nay có khá nhiều developers bao gồm cả những bạn mới và những bạn có kinh nghiệm vẫn còn khá mơ hồ về 2 khái niệm cũng như cách áp dụng nó với Spring. Vì vậy mình xin mạn phép viết một bài chia sẻ về vấn đề này. Đầu tiên hãy tìm hiểu về Inversion of Control(IoC)
2. Inversion of Control(IoC) là gì?
Inversion of Control có thể được hiểu là một nguyên lý thiết kế trong công nghệ phần mềm. Kiến trúc phần mềm được được áp dụng thiết kế này sẽ đảo ngược quyền điều khiển so với kiểu lập trình hướng thủ tục. Trong lập trình hướng thủ tục, các đoạn mã được thêm vào sẽ gọi các thư viện nhưng với IoC thì những IoC container sẽ sẽ chích những dependencies vào khi nó khởi tạo bean.
Khái niệm này đọc thì có thể hiểu được nhưng còn rất trừu tượng cũng đành chịu thôi, vì bản chất của nó là trừu tượng mà.
3. Dependency Injection(DI)
Dependency Injection là một partern để mà implement Inversion of Control(IoC). Các dependencies sẽ được chích(inject) vào module khi khởi tạo.
Dưới đây là cách chúng ta tạo một dependency object trong một ứng dụng truyền thống
public class TraditionalStore {
private Item item;
public TraditionalStore(){
item = new ItemImplOne();
}
}
Trong ví dụ ở trên chúng ta phải chỉ định rõ ràng class implementation cho interface Item. Tuy nhiên, bằng cách sử dụng phương pháp DI chúng ta có thề viết lại ví dụ trên như sau:
public class DIStore {
private Item item;
public DIStore(Item item){
this.item = item;
}
}
4. Spring IoC Container
Interface org.springframework.context.ApplicationContext đại diện cho Spring IoC Container và chịu trách nhiệm cài đặt(installation), cấu hình(configuration) và tập hợp(assembling) những object beans và cũng quản lý luôn lifecycle của chúng.
Spring cung cấp 2 tùy chọn implementation cho ApplicationContext interface: ClassPathXmlApplicationContext và FileSystemXmlApplicationContext sử dụng cho ứng dụng độc lập và WebApplicationContext cho ứng dụng web.
Dưới đây là cách chúng ta khởi tạo thủ công(manually) một container:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Có 3 cách để thực hiện Dependency Injection trong Spring bao gồm: constructor, setter và field
5. Constructor Dependency Injection
Với phương pháp này, Spring container sẽ gọi một constructor với những arguments được đại diện cho các dependencies. Hãy xem ví dụ về cấu hình bean với annotation dưới đây
@Configuration
public class StoreConfiguration {
@Bean
public Item itemOne(){
return new ItemImplOne();
}
@Bean DIStore store(){
return new DIStore(itemOne());
}
}
@Configuration annotation chỉ ra rằng đây là một class nguồn của các bean được định ngĩa bên trong
@Bean annotation trên từng method để để định nghĩa một bean
<bean id="itemOne" class="org.khoa.nguyen.dang.di.ItemImplOne"></bean>
<bean id="store" class="org.khoa.nguyen.dang.di.DIStore">
<constructor-arg type="ItemImplOne" index="0" name="item" ref="itemOne"/>
</bean>
Ví dụ trên là cách cấu hình bean bằng cách sử dụng XML configuration
6. Setter Dependency Injection
Trong setter DI, Spring container sẽ setter methods của class sau khi đã gọi một no-argument constructor hoặc no-argument static factory method để khởi tạo bean
@Bean
public Item itemOne(){
return new ItemImplOne();
}
@Bean
public Store store(){
Store store = new Store();
store.setItem(itemOne());
return store;
}
Tiếp theo, hãy định nghĩa bean sử dụng XML configuration
<bean id="itemOne" class="org.khoa.nguyen.dang.di.ItemImplOne" />
<bean id="store" class="org.khoa.nguyen.dang.di.Store">
<property name="item" ref="itemOne"></property>
</bean>
Container DI và Setter DI là 2 cách gần như nhau đều có thể giải quyết chung một bài toán. Tuy nhiên, chúng ta hãy sử dụng constructor DI cho mandatory dependencies còn setter DI cho optional dependencies
7. Field Dependency Injection
Một cách khác để chúng ta khởi tạo một bean cho ứng dụng đó là thêm vào @Autowired vào các thuộc tính như dưới đây
public class AutowiredStore {
@Autowired
private Item item;
}
Trong khi khởi tạo AutowiredStore object, nếu không có bất kỳ constructor hoặc method nào để inject Item bean, Spring container sẽ sử dụng reflection để inject Item vào trong AutowiredStore.
Đây là một cách dễ dàng để chúng ta khởi tạo một bean, tuy nhiên cũng vì quá dễ dàng nên sẽ gây ra một số vấn đề dưới đây và vì vậy nó không được khuyến khích sử dụng
- Vi phạm Single Responsibility Principle: Bởi vì dễ dàng sử dụng nên các developers thường khai báo hàng tá các dependencies vào trong 1 class và nó vô tình vi phạm vào Single Responsibility Principle(Một class chỉ nên làm một nhiệm vụ duy nhất)
- Sử dụng reflection để cấu hình bean cũng có nghĩa là chúng ta sẽ tốn resource hơn so với constructor và setter DI
8. Autowiring Dependencies
Spring bean wiring cho phép container tự động giải quyết các dependencies giữa các beans bằng cách kiểm tra các bean đã được định nghĩa. Có 4 loại để autowiring một bean bằng cách sử dụng XML Configuration
- No: giá trị mặc định - không sử dụng autowiring cho bean, chúng ta phải chỉ rõ tên của các dependencies
- byName: Autowiring được thực hiện dựa vào tên của thuộc tính, Spring container sẽ tìm kiếm 1 bean name có sẵn có name trùng với name của thuộc tính
- byType: Autowiring được thực hiện dựa vào kiểu của thuộc tính, Spring container sẽ tìm kiếm 1 bean có loại giống với loại của thuộc tính. Nếu có nhiều hơn 1 bean cùng loại, Spring sẽ throw 1 exception
- constructor: Autowiring sẽ dựa trên constructor arguments meaning. Spring container sẽ tìm kiếm beans mà có type giống như type của constructor arguments
Sử dụng @Autowired annotation để khởi tạo bean byType và @Qualifier để chỉ định bean name
public class AutowiringStore {
@Autowired
@Qualifier("itemTwo")
private Item item;
}
Tiếp theo, hãy inject bean item vào trong item property của storeOne bean bằng cách sử dụng XML như sau
<bean id="itemTwo" class="org.khoa.nguyen.dang.di.ItemImplTwo" />
<bean id="storeOne" class="org.khoa.nguyen.dang.di.Store" autowire="byName" />
9. Lazy Initialized Bean
Mặc định, Spring container sẽ khởi tạo và cấu hình toàn bộ Singleton beans khi startup application. Để ngăn cản điều này hay nếu chúng ta chỉ muốn khởi tạo bean khi có request thì sử dụng lazy-init attribute với value bằng true như dưới đây
<bean id="item" class="org.khoa.nguyen.dang.di.ItemImplTwo" lazy-init="true"></bean>
10. Một số link tham khảo
https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring