The Spring IoC Container
Giới thiệu
Spring IoC là phần được giới thiệu đầu tiên trong Spring core framework, nó là một trong những thành phần quan trọng nhất của Spring. Khi nói về Spring thì chúng ta không thể không nói đến IoC, nếu ai đó không biết về Spring IoC thì thật khó để nói rằng người đó hiểu biết về Spring. Hiện nay khá có nhiều Coder mặc dù là những coder có kinh nghiệm thì cũng còn chưa nắm rõ về IoC. Bài viết này sẽ cùng các bạn tìm hiểu IoC, các khái niệm và cách nó vận hành. Trước khi tìm hiểu về Spring IoC hãy tìm hiểu về khái niệm quan trọng là Inversion of Control(IoC) nhé.
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ẽ chích(inject) những dependencies vào khi nó khởi tạo bean.
Một số lợi ích của kiến trúc này
- Tách riêng việc thực thi một nhiệm vụ từ việc thực hiện nó
- Làm cho việc chuyển đổi giữa các implementations dễ dàng hơn
- Module của ứng dụng lớn hơn
- Dễ dàng hơn trong việc test chương trình bằng cách tách một thành phần hoặc mock các dependencies của nó, cho phép chúng communicate thông qua contracts
Invesion of Control có thể đạt được thông qua việc apply các mẫu: Strategy design pattern, Service Locator pattern, Factory pattern, and Dependency Injection(DI)
Spring IoC Container Overview
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 đối tượng beans và cũng quản lý luôn lifecycle của chúng.
Spring cung cấp 2 tùy chọn implementations cho ApplicationContext interface là: ClassPathXmlApplicationContext và FileSystemXmlApplicationContext sử dụng cho ứng dụng độc lập và WebApplicationContext cho ứng dụng web.
Để tập hợp các beans, Spring container sẽ sử dụng configuration metadata ở 2 dạng là XML Configuration và Annotation.
Dưới đây là cách chúng ta khởi tạo thủ công(manually) một container:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
Spring container sẽ đọc các file metadata(services.xml, daos.xml) này và sử dụng nó để tập hợp các beans lúc runtime.
Dependency Injection trong Spring có 3 cách để thực hiện là: Constructor, Setter và Field
Spring Bean Overview
Một Spring IoC Container quản lý một hoặc nhiều beans. Các beans này được khởi tạo với configuration metadata được cung cấp cho container ví dụ như trong file XML services.xml ở ví dụ trên. Các beans được định nghĩa chứa các thông tin metadata như dưới đây.
- A package-qualified class name: thông thường là các class implementations của các beans được định nghĩa
- Bean behavioral: Trạng thái của các beans trong container(scope, lifecycle callbacks, and so forth)
- References to other beans: Cần thiết để beans có thể work, cũng có thể được gọi là collaborators hay dependencies
- Other configuration settings: ví dụ như số lượng connections trong beans để quản lý connection pool hoặc size limit của pool.
Mỗi bean đều có một hoặc nhiều định danh. Các định danh này phải là duy nhất trong container. Một bean thông thường có một định danh duy nhất, tuy nhiên nếu yêu cầu nhiều hơn một thì sử dụng alias.
Khởi tạo(Instantiation) bean với một Constructor
Hầu hết Spring users khuyến khích sử dụng JavaBean với duy nhất mặc định một constructor với no-argument và sử dụng getters và setters. Tuy nhiên chúng ta vẫn có thể khởi tạo bean với constructor như phần dưới đây.
Khởi tạo bean với một static factory method
Khi định nghĩa một bean với một static-factory-method, sử dụng class atrribute để chỉ định class chứa static-factory-method và factory-method attribute để chỉ định tên của static-factory-method đó.
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
Khởi tạo bean với một instance(non-static) factory method
Giống như cách khởi tạo bean với static-factory-method ở trên, để khởi tạo bean cho non-static-factory-method hãy để trống class attribute và sử dụng factory-bean attribute để chỉ định tên của bean sẽ được gọi.
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
}
Dependency Injection(DI) là gì?
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 đó là ItemImplOne. 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;
}
}
Constructor-Based 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
Setter-Based Dependency Injection
Với setter DI, Spring container sẽ gọi 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
public class Store {
// the Store has a dependency on the Item
private Item item;
// setter method so that the Spring container can inject a Item
public void setItem(Item item) {
this.item = item;
}
}
Tiếp theo, hãy định nghĩa bean sử dụng XML configuration
<bean id="item" class="org.khoa.nguyen.dang.di.Item" />
<bean id="store" class="org.khoa.nguyen.dang.di.Store">
<property name="item" ref="item"></property>
</bean>
Constructor-based or setter-based DI?
Container DI và Setter DI gần như nhau đều có thể giải quyết chung một bài toán. Tuy nhiên, có một convention đó là chúng ta hãy sử dụng constructor DI cho mandatory dependencies còn setter DI cho optional dependencies. Lưu ý rằng chúng ta có thể sử dụng @Required annotation để chỉ rằng thuộc tính đó là require.
Field-Based 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
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
Ví dụ dưới đây autowire item bean by type vào Store bean
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item item){
this.item = item;
}
}
Chúng ta cũng có thể inject beans sử dụng @Autowired annonotation để autowiring by type
public class Store {
@Autowired
private Item item;
}
Trong trường hợp có nhiều hơn một beans of same type, chúng ta có thể sử dụng @Qualifier annotation để refer tới bean by name
public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}
Autowire by Type bằng cách sử dụng XML configuration
<bean id="store" class="org.khoa.nguyen.dang.di.Store" autowire="byType" />
Tiếp theo, hãy inject bean item vào trong item property của storeOne bean by Name bằng XML
<bean id="itemTwo" class="org.khoa.nguyen.dang.di.ItemImplTwo" />
<bean id="storeOne" class="org.khoa.nguyen.dang.di.Store" autowire="byName" />
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>
Dependency resolution process(Quá trình xử lý) như thế nào?
Spring container sẽ handle các bean dependencies như sau:
- ApplicationContext được tạo và khởi tạo với configuration metadata mà các beans được định nghĩa ở đó. Các configuration metadata có thể là XML, Java code, hay annotations.
- Từng bean, các dependencies của chúng ở dạng properties, constructor arguments, hay static-factory-method sẽ được cung cấp cho bean khi khởi tạo(Spring container sẽ khởi tạo các dependencies bean trước)
- Mỗi property hay argument constructor là một definition value để có thể set vào hay refer tới một bean khác
- Mỗi property hay constructor argument là một value được convert từ định dạng của nó. Mặc định Spring sẽ convert một giá trị được cung cấp ở dạng String format thành built-in type như là int, String, long, boolean, etc
Kết luận
Trên đây tôi đã trình bày khái niệm về DI, IoC, Bean trong Spring Framework. Để tìm hiểu rõ hơn các bạn nên tham khảo một số bài viết sau:
Đây là bài viết đầu tiên của mình về Spring Framework mình luôn welcome các ý kiến trong phần bình luận nhé.
Bài viết tiếp theo trong series Spring Framework mình sẽ trình bày về Bean Scope