
1. Sơ lược
2. Inversion of Control là gì?
Inversion of Control là một nguyên tắc trong kỹ thuật phần mềm chuyển quyền kiểm soát các đối tượng hoặc các phần của chương trình sang một vùng chứa hoặc khuôn khổ. Chúng ta thường sử dụng nó nhất trong ngữ cảnh của lập trình hướng đối tượng.Trái ngược với lập trình truyền thống, trong đó mã tùy chỉnh của chúng tôi thực hiện các lệnh gọi đến thư viện, IoC cho phép một khuôn khổ kiểm soát luồng chương trình và thực hiện các lệnh gọi đến mã tùy chỉnh của chúng tôi. Để kích hoạt điều này, các framework sử dụng các phần trừu tượng với hành vi bổ sung được tích hợp sẵn. Nếu chúng ta muốn thêm hành vi của riêng mình, chúng ta cần mở rộng các lớp của khung công tác hoặc bổ sung các lớp của riêng chúng ta.
Ưu điểm của kiến trúc này là:
ü tách việc thực thi một nhiệm vụ khỏi việc triển khai nó.
ü giúp chuyển đổi giữa các triển khai khác nhau dễ dàng hơn.
ü mô đun lớn hơn của một chương trình.
ü dễ dàng hơn trong việc kiểm tra một chương trình bằng cách cô lập một thành phần hoặc bắt trước các thành phần phụ thuộc của nó và cho phép các thành phần giao tiếp thông qua các hợp đồng.
Chúng ta có thể đạt được Inversion of Control thông qua các cơ chế khác nhau như: Strategy design pattern, Service Locator pattern, Factory pattern, và Dependency Injection (DI).
Tiếp theo, chúng ta sẽ xem xét DI tiếp theo.
3. Dependency Injection (DI) là gì?
Dependency Injection là một mẫu mà chúng ta có thể sử dụng để triển khai IoC, trong đó Inversion of Control là thiết lập các phụ thuộc của một đối tượng.
Việc kết nối các đối tượng với các đối tượng khác, hoặc "tiêm" đối tượng vào các đối tượng khác, được thực hiện bởi một trình lắp ráp chứ không phải bởi chính các đối tượng.
Đây là cách chúng tôi tạo một đối tượng "phụ thuộc" trong lập trình truyền thống:
public class Store {
private Item item;
public Store() { item = new ItemImpl1();
}
}
Trong ví dụ trên, chúng ta cần khởi tạo một triển khai của interface Item trong chính lớp Store .
Bằng cách sử dụng DI, chúng ta có thể viết lại ví dụ mà không chỉ định việc triển khai Item mà chúng ta muốn:
public class Store {
private Item item;
public Store(Item item)
{ this.item = item; }
}
Trong các phần tiếp theo, chúng tôi sẽ xem xét cách chúng tôi có thể cung cấp triển khai Item thông qua siêu dữ liệu.
Cả IoC và DI đều là những khái niệm đơn giản, nhưng chúng có ý nghĩa sâu sắc trong cách chúng ta cấu trúc hệ thống của mình, vì vậy chúng rất đáng để hiểu đầy đủ.
4. Spring IoC Container
Một IoC Container là đặc điểm chung của các khuôn khổ triển khai IoC.
Trong Spring Framework, interface ApplicationContext đại diện cho IoC Container. Spring container chịu trách nhiệm khởi tạo, cấu hình và lắp ráp các đối tượng được gọi là bean, cũng như quản lý vòng đời của chúng.
Spring Framework cung cấp một số triển khai của interface ApplicationContext : ClassPathXmlApplicationContext và FileSystemXmlApplicationContext cho các ứng dụng độc lập và WebApplicationContext cho các ứng dụng web.
Để tập hợp các bean, vùng chứa sử dụng siêu dữ liệu cấu hình, có thể ở dạng cấu hình XML hoặc annotations.
Đây là một cách để khởi tạo vùng chứa theo cách thủ công:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Để đặt thuộc tính item trong ví dụ trên, chúng ta có thể sử dụng siêu dữ liệu. Sau đó, vùng chứa sẽ đọc siêu dữ liệu này và sử dụng nó để tập hợp các bean trong runtime.
Dependency Injection trong Spring có thể được thực hiện thông qua các constructor, setters hoặc fields.
5. Dependency Injection dựa trên Constructor
Trong trường hợp Dependency Injection dựa trên Constructor , vùng chứa sẽ gọi một phương thức constructor với các đối số đại diện cho một Dependency("phụ thuộc") mà chúng ta muốn đặt.
Spring giải quyết mỗi đối số chủ yếu theo kiểu, theo sau là tên của thuộc tính và chỉ mục để phân định. Hãy xem cấu hình của bean và các phụ thuộc của nó bằng cách sử dụng chú thích:
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}
Các chú thích @Configuration chỉ ra rằng lớp là một nguồn cung cấp các định nghĩa bean. Chúng tôi cũng có thể thêm nó vào nhiều lớp cấu hình.
Chúng tôi sử dụng chú thích @Bean trên một phương thức để xác định bean. Nếu chúng ta không chỉ định tên tùy chỉnh, thì tên bean sẽ mặc định là tên phương thức.
Đối với một bean có phạm vi mặc định là singleton, trước tiên Spring sẽ kiểm tra xem một phiên bản bean được lưu trong bộ nhớ cache đã tồn tại hay chưa và chỉ tạo một phiên bản mới nếu nó chưa tồn tại. Nếu chúng ta đang sử dụng phạm vi nguyên mẫu , container sẽ trả về một cá thể bean mới cho mỗi lần gọi phương thức.
Một cách khác để tạo cấu hình của bean là thông qua cấu hình XML:
<bean id="item1" class="org.baeldung.store.ItemImpl1"
/>
<bean id="store" class="org.baeldung.store.Store">
<constructor-arg type="ItemImpl1" index="0"
name="item" ref="item1"
/>
</bean>
6. Dependency Injection dựa trên Setter
Đối với DI dựa trên setter, container sẽ gọi các phương thức setter của lớp chúng ta sau khi gọi một phương thức Constructor không đối số hoặc phương thức static factory không đối số để khởi tạo bean. Hãy tạo cấu hình này bằng cách sử dụng chú thích:
@Bean
public Store store() {
Store store = new Store();
store.setItem(item1());
return store;
}
Chúng ta cũng có thể sử dụng XML cho cùng một cấu hình bean:
<bean id="store" class="org.baeldung.store.Store">
<property name="item" ref="item1"
/>
</bean>
Chúng ta có thể kết hợp Dependency Injection dựa trên constructor và dựa trên setter cho cùng một bean. Tài liệu Spring khuyến nghị sử dụng phương thức Dependency Injection dựa trên Constructor cho các phụ thuộc bắt buộc và Dependency Injection dựa trên setter cho các phụ thuộc tùy chọn.
7. Dependency Injection dựa trên Fields
Trong trường hợp DI dựa trên field, chúng tôi có thể đưa vào các phần phụ thuộc bằng cách đánh dấu chúng bằng chú thích @Autowired mong muốn :public class Store {
@Autowired
private Item item;
}
Trong khi xây dựng đối tượng Store, nếu không có phương thức consstructor hoặc setter nào để đưa Item bean, vùng chứa sẽ sử dụng reflection để đưa Item vào Store .
Chúng tôi cũng có thể đạt được điều này bằng cách sử dụng cấu hình XML .
Cách tiếp cận này có thể trông đơn giản và gọn gàng hơn, nhưng chúng tôi không khuyên bạn nên sử dụng nó vì nó có một số nhược điểm như:
Ø Phương pháp này sử dụng sự reflection để đưa các phụ thuộc vào, tốn kém hơn so với việc sử dụng Dependency Injection dựa trên phương thức constructor hoặc dựa trên.
Bạn có thể tìm thêm thông tin về chú thích @Autowired trong bài viết Wiring In Spring.
8. Autowiring Dependency
Wiring cho phép Spring container tự động giải quyết các mối quan hệ phụ thuộc giữa các bean cộng tác bằng cách kiểm tra các bean đã được xác định.Có bốn chế độ autowiring một bean bằng cách sử dụng cấu hình XML:
Ø no: giá trị mặc định - điều này có nghĩa là không có autowiring nào được sử dụng cho bean và chúng ta phải đặt tên rõ ràng cho các phần phụ thuộc.
Ví dụ: chúng ta hãy tự động truyền tải bean item1 được định nghĩa ở trên theo kiểu vào trong store bean:
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item
item){
this.item = item;
}
}
Chúng tôi cũng có thể tiêm bean bằng cách sử dụng @Autowired cho autowiring theo loại:
public class Store {
@Autowired
private Item item;
}
Nếu có nhiều hơn một bean cùng loại, chúng ta có thể sử dụng chú thích @Qualifier để tham chiếu đến một bean theo tên:
public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}
Bây giờ chúng ta hãy autowire bean theo loại thông qua cấu hình XML:
<bean id="store" class="org.baeldung.store.Store"
autowire="byType"> </bean>
Tiếp theo, hãy đưa một mục có tên bean vào thuộc tính item của store bean theo tên thông qua XML:
<bean id="item" class="org.baeldung.store.ItemImpl1"
/>
<bean id="store" class="org.baeldung.store.Store"
autowire="byName">
</bean>
Chúng ta cũng có thể ghi đè autowiring bằng cách xác định các phụ thuộc một cách rõ ràng thông qua các đối số của constructor hoặc setter.
9. Lazy Initialized Beans
Theo mặc định, container tạo và cấu hình tất cả các bean đơn trong quá trình khởi tạo. Để tránh điều này, chúng ta có thể sử dụng thuộc tính lazy-init với giá trị true trên cấu hình bean:
Do đó, bean item1 sẽ chỉ được khởi tạo khi nó được yêu cầu lần đầu chứ không phải khi khởi động. Ưu điểm của việc này là thời gian khởi tạo nhanh hơn, nhưng điều đáng nói là chúng tôi sẽ không phát hiện ra bất kỳ lỗi cấu hình nào cho đến khi bean được yêu cầu, có thể là vài giờ hoặc thậm chí vài ngày sau khi ứng dụng đã chạy.
10. Kết luận
Trong bài viết này, chúng tôi đã trình bày các khái niệm về Inversion of Control và Dependency Injection, và ví dụ chúng trong Spring Framework.Chúng ta có thể đọc thêm về những khái niệm này trong các bài viết của Martin Fowler(kiến thức khá sâu nên mình không dịch lại):
Inversion of Control Containers and the Dependency Injection pattern.
Inversion of Control
0 Nhận xét