Spring IoC Container chịu trách nhiệm quản lý vòng đời và cấu hình của các đối tượng trong ứng dụng, hay còn gọi là các bean. Những bean này được khởi tạo dựa trên metadata cấu hình mà bạn cung cấp, có thể ở dạng XML, annotation hay Java code.
Bên trong container, các cấu hình này được ánh xạ thành các đối tượng BeanDefinition. Mỗi BeanDefinition chứa các thông tin metadata quan trọng bao gồm:
- Tên lớp đầy đủ (package-qualified class name): Là lớp thực tế được sử dụng để tạo thể hiện bean.
- Cấu hình hành vi (Behavioral configuration): Xác định cách bean hoạt động trong container như scope (singleton, prototype), các callback trong vòng đời, v.v.
- Tham chiếu đến các bean khác: Các dependency cần thiết để bean thực hiện chức năng của nó (còn gọi là collaborators).
- Các cài đặt cấu hình khác: Các thuộc tính cấu hình chi tiết khác như kích thước pool, thông tin kết nối, v.v.
Ngoài ra, ApplicationContext cho phép đăng ký các đối tượng đã tồn tại (được tạo bên ngoài container) vào Spring quản lý. Thông qua phương thức getBeanFactory(), bạn có thể lấy được implementation DefaultListableBeanFactory và sử dụng các phương thức như registerSingleton(..) hoặc registerBeanDefinition(..). Tuy nhiên, cần lưu ý rằng việc đăng ký này nên thực hiện ở giai đoạn đầu của ứng dụng để tránh các lỗi về đồng bộ hóa hoặc trạng thái không nhất quán khi container đang chạy.
Quy tắc đặt tên cho Bean
Mỗi bean phải có một hoặc nhiều định danh (identifier) là duy nhất trong phạm vi container đó. Thông thường, một bean chỉ có một tên, nhưng bạn có thể định nghĩa nhiều alias (bí danh) cho nó.
Trong cấu hình XML, bạn sử dụng thuộc tính id hoặc name để gán tên. id thường dùng để định danh chính xác, trong khi name có thể chứa nhiều tên được phân cách bởi dấu phẩy, chấm phẩy hoặc khoảng trắng. Nếu bạn không cung cấp id hay name, container sẽ tự động sinh ra một tên duy nhất cho bean đó.
Quy ước đặt tên chuẩn trong Spring là tuân theo quy tắc đặt tên biến của Java: bắt đầu bằng chữ thường và viết hoa các chữ cái đầu của các từ tiếp theo (camelCase). Ví dụ: userController, orderService, paymentDao. Việc đặt tên nhất quán giúp cấu hình dễ đọc và hỗ trợ tốt khi sử dụng Spring AOP.
Khi sử dụng Component Scanning, Spring sẽ tự động tạo tên bean dựa trên tên lớp. Tên lớp được chuyển đổi thành chữ thường cho chữ cái đầu tiên. Tuy nhiên, với các từ viết tắt đặc biệt (ví dụ 2 chữ cái đầu đều viết hoa như URLParser), quy tắc này sẽ giữ nguyên chữ cái đầu để đảm bảo tính đúng đắn về ngữ nghĩa.
Định nghĩa Alias cho Bean
Trong một số trường hợp, bạn muốn một bean được gọi bằng nhiều tên khác nhau trong các bối cảnh khác nhau. Bạn có thể định nghĩa alias ngay trong thẻ bean hoặc sử dụng thẻ <alias/> độc lập.
Ví dụ, bạn có một hệ thống lớn gồm nhiều subsystem. Subsystem A gọi DataSource là subsystemA-ds, Subsystem B gọi là subsystemB-ds, nhưng cả hai đều dùng chung một bean chính tên là main-ds. Bạn có thể cấu hình alias như sau:
<alias name="main-ds" alias="subsystemA-ds"/>
<alias name="main-ds" alias="subsystemB-ds"/>
Cách tiếp cận này giúp tách biệt tên gọi giữa các module, tránh xung đột tên nhưng vẫn chia sẻ cùng một instance.
Cơ chế khởi tạo (Instantiation) của Bean
Bean definition đóng vai trò như một công thức (recipe) để tạo đối tượng. Container sẽ sử dụng metadata này để tạo ra instance khi có yêu cầu. Thuộc tính class trong cấu hình XML (hoặc metadata tương đương) là thành phần quan trọng nhất, thường được dùng theo hai cách chính:
- Khởi tạo trực tiếp bằng Constructor: Container sử dụng reflection để gọi constructor của lớp được chỉ định.
- Khởi tạo qua Static Factory Method: Container gọi một phương thức tĩnh của lớp được chỉ định để trả về đối tượng.
Đối với các lớp lồng nhau (nested class), bạn sử dụng ký hiệu $ hoặc dấu chấm . để tham chiếu. Ví dụ: com.example.OuterClass$InnerClass.
Sử dụng Constructor
Đây là cách phổ biến nhất. Spring có thể quản lý hầu hết các lớp Java thông thường, miễn là chúng có constructor hợp lý. Bạn không cần implement bất kỳ interface đặc biệt nào.
<bean id="transactionProcessor" class="com.app.service.TransactionProcessor"/>
Sử dụng Static Factory Method
Nếu bạn muốn ủy quyền việc tạo đối tượng cho một phương thức tĩnh (thường thấy trong legacy code), bạn hãy khai báo lớp chứa phương thức đó trong thuộc tính class và tên phương thức trong factory-method.
<bean id="systemConfig"
class="com.app.config.SystemConfig"
factory-method="getDefaultInstance"/>
Ví dụ về lớp Java tương ứng:
public class SystemConfig {
private static final SystemConfig INSTANCE = new SystemConfig();
private SystemConfig() {}
public static SystemConfig getDefaultInstance() {
return INSTANCE;
}
}
Sử dụng Instance Factory Method
Khác với static factory, cách này yêu cầu một bean khác đã tồn tại trong container sẽ thực hiện việc tạo bean mới thông qua một phương thức không tĩnh. Bạn bỏ qua thuộc tính class và dùng factory-bean để trỏ đến bean chứa factory method.
<!-- Bean đóng vai trò là nhà máy sản xuất các service khác -->
<bean id="serviceFactory" class="com.app.factory.ServiceFactory"/>
<!-- Bean được tạo ra thông qua phương thức của factory -->
<bean id="emailService"
factory-bean="serviceFactory"
factory-method="createEmailService"/>
Code Java minh họa:
public class ServiceFactory {
public EmailService createEmailService() {
return new EmailServiceImpl();
}
public NotificationService createNotificationService() {
return new NotificationServiceImpl();
}
}
Lưu ý phân biệt khái niệm "factory bean" (bean dùng để tạo bean khác) và interface FactoryBean (một interface đặc biệt của Spring).
Xác định kiểu runtime của Bean
Vì bean có thể được tạo bởi factory method, bị wrap bởi proxy AOP, hoặc là kết quả của logic phức tạp, nên kiểu khai báo trong metadata có thể không giống với kiểu thực tế tại runtime. Để lấy chính xác kiểu của đối tượng được trả về bởi container, hãy sử dụng phương thức BeanFactory.getType(String beanName). Phương thức này sẽ xử lý tất cả các trường hợp đặc biệt và trả về kiểu đúng nhất của bean.