BeanFactory vs ApplicationContext: Bản Chất Hai Container
Mổ xẻ bản chất BeanFactory và ApplicationContext: cấu trúc singletonObjects, beanDefinitionMap, cơ chế delegation và 4 tính năng enterprise.
TL;DR: BeanFactory là interface gốc định nghĩa hợp đồng tra cứu bean cơ bản nhất (getBean), hoạt động theo cơ chế nạp lười (lazy). ApplicationContext kế thừa hợp đồng này nhưng không triển khai lại từ đầu mà sử dụng mẫu Composition: bọc một instance DefaultListableBeanFactory bên trong và ủy quyền (delegate) toàn bộ thao tác quản lý bean, đồng thời nạp sẵn (eager) toàn bộ singleton lúc khởi động và bổ sung 4 năng lực enterprise. Bên dưới, toàn bộ container thực chất vận hành dựa trên hai bản đồ băm chính: beanDefinitionMap (lưu siêu dữ liệu) và singletonObjects (lưu instance thực tế).
Khi học về Inversion of Control: Bản Chất Đảo Quyền Điều Khiển, bạn đã biết Container nắm quyền điều phối toàn bộ đối tượng. Khi câu lệnh SpringApplication.run() trả về một context, thực chất đối tượng đó chứa những cấu trúc dữ liệu nào bên trong JVM? Bài viết này sẽ bóc tách lớp vỏ trừu tượng để trả lời trực diện câu hỏi đó.
Hợp đồng tối thiểu của BeanFactory quy định những gì?
Nằm ở gốc rễ của cây phân cấp IoC Container, BeanFactory là một interface rất cô đọng:
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name);
Class<?> getType(String name);
}
Ý nghĩa thiết kế của BeanFactory gói gọn trong một nhiệm vụ: Cung cấp định danh hoặc kiểu dữ liệu, container sẽ trả về một đối tượng đã được khởi tạo và tiêm đầy đủ phụ thuộc.
Đặc điểm quan trọng: BeanFactory hoạt động hoàn toàn theo cơ chế Lazy Loading — đối tượng chỉ thực sự được cấp phát khi có lời gọi getBean() đầu tiên.
Cấu trúc dữ liệu thật trong DefaultListableBeanFactory gồm những gì?
Interface BeanFactory chỉ là bản hợp đồng. Lớp thực thi nòng cốt phụ trách toàn bộ tác vụ của Spring Container là DefaultListableBeanFactory. Lớp này duy trì hai cấu trúc dữ liệu tách biệt:

beanDefinitionMap(Map<String, BeanDefinition>): Bảng ánh xạ lưu trữ Metadata — mô tả tên bean, class tương ứng, phạm vi (Scope), và các phụ thuộc cần tiêm. Ở giai đoạn này, đối tượng Java thật sự chưa hề tồn tại.singletonObjects(Map<String, Object>): Bảng băm lưu trữ Instance thật — các đối tượng đã được gọi constructor, tiêm thuộc tính và chạy xong các phương thức khởi tạo vòng đời.
Sự phân tách này là cơ sở để Spring chia quy trình khởi động thành hai pha: đăng ký siêu dữ liệu trước, cấp phát đối tượng sau.
Tại sao thao tác getBean() từ lần thứ hai trở đi luôn đạt tốc độ O(1)?
Khi bạn yêu cầu một bean thông qua lệnh getBean("orderService"), phương thức nội bộ AbstractBeanFactory.doGetBean() vận hành theo quy trình:

- Bước 1: Tra cứu bảng băm
singletonObjects. Nếu đối tượng đã tồn tại, lập tức trả về ngay trong thời gian $O(1)$. - Bước 2: Nếu chưa có, kích hoạt phương thức
createBean(). Container đọc siêu dữ liệu từbeanDefinitionMap, chọn constructor phù hợp, đệ quy gọigetBean()để tiêm các phụ thuộc, và gọi callback@PostConstruct. - Bước 3: Đưa đối tượng hoàn chỉnh vừa tạo vào
singletonObjectsđể phục vụ các lần tra cứu tiếp theo.
Khái niệm "Singleton" trong Spring thực chất là: Một instance duy nhất được lưu trữ trong map singletonObjects của một container cụ thể, hoàn toàn không phải Singleton Pattern dùng biến static toàn cục của Java.
Mối quan hệ giữa ApplicationContext và BeanFactory được tổ chức ra sao?
Nhiều người lầm tưởng ApplicationContext kế thừa trực tiếp để mở rộng mã nguồn từ BeanFactory. Trên thực tế, các lớp hiện thực như AnnotationConfigApplicationContext áp dụng mẫu thiết kế Composition & Delegation:
public class GenericApplicationContext extends AbstractApplicationContext {
// BeanFactory that su nam o truong du lieu nay
private final DefaultListableBeanFactory beanFactory;
@Override
public Object getBean(String name) {
assertBeanFactoryActive();
// Uy quyen truc tiep xuong DefaultListableBeanFactory
return this.beanFactory.getBean(name);
}
}
ApplicationContext hiện thực (implements) interface BeanFactory về mặt hợp đồng, nhưng sở hữu (has-a) một instance DefaultListableBeanFactory bên trong và ủy quyền toàn bộ việc lấy bean cho đối tượng này.

Khác biệt mấu chốt: ApplicationContext khởi tạo Eager toàn bộ các singleton bean ngay tại thời điểm ứng dụng khởi động. Điều này giúp phát hiện lỗi cấu hình (thiếu bean, sai kiểu, lỗi khởi tạo) ngay lập tức (Fail-Fast) trước khi ứng dụng tiếp nhận lưu lượng mạng.
Bốn khả năng mở rộng cấp enterprise nào được ApplicationContext bổ sung?
Bên cạnh việc ủy quyền quản lý bean cho DefaultListableBeanFactory, ApplicationContext tích hợp sẵn 4 giao diện tính năng mở rộng:
| Khả năng mở rộng | Interface tương ứng | Ứng dụng thực tế |
|---|---|---|
| Environment & Properties | EnvironmentCapable | Đọc cấu hình @Value("${...}"), kích hoạt Profile |
| Đa ngôn ngữ (i18n) | MessageSource | Nạp file thông báo messages.properties |
| Phát sự kiện (Event Bus) | ApplicationEventPublisher | Bắn và lắng nghe sự kiện nội bộ qua @EventListener |
| Nạp tài nguyên thống nhất | ResourcePatternResolver | Đọc file từ classpath:, file:, hoặc mạng URL |
Cấu trúc bộ nhớ heap thực tế của một Spring Container trông như thế nào?
Nhìn dưới góc độ phân bổ vùng nhớ heap của JVM, một instance Spring Container chính là tập hợp các bảng băm phối hợp chặt chẽ:
AnnotationConfigApplicationContext
└─ beanFactory: DefaultListableBeanFactory
├─ beanDefinitionMap : LinkedHashMap<String, BeanDefinition>
├─ singletonObjects : ConcurrentHashMap<String, Object>
├─ earlySingletonObjects : ConcurrentHashMap<String, Object>
└─ singletonFactories : HashMap<String, ObjectFactory<?>>
singletonObjectslàConcurrentHashMap, cho phép hàng ngàn luồng HTTP đọc bean đồng thời mà không bị tranh chấp (lock-free reads).beanDefinitionMaplàLinkedHashMap, duy trì chính xác thứ tự đăng ký định nghĩa bean từ các file cấu hình.
Những cạm bẫy thường gặp về phạm vi Singleton và ngữ cảnh Container?
- Khởi tạo 2 Context trong cùng một JVM: Nếu bạn tạo
ctx1vàctx2từ cùng một cấu hình, mỗi context sẽ sở hữu mộtDefaultListableBeanFactoryđộc lập. Bean singleton ởctx1vàctx2là hai thực thể hoàn toàn khác nhau trên bộ nhớ, không hề chia sẻ trạng thái. - Nhầm lẫn Singleton Spring với Static Singleton: Singleton trong Spring chỉ có phạm vi (scope) trong phạm vi container chứa nó. Nếu ứng dụng chạy nhiều container (ví dụ trong mô hình module hóa hoặc hierarchy context), bean singleton sẽ có nhiều phiên bản.
- Tiêm ApplicationContext để gọi getBean thủ công: Việc gọi
ctx.getBean()tùy tiện biến mã nguồn thành Service Locator, làm ẩn phụ thuộc và triệt tiêu tính minh bạch của thiết kế. Hãy luôn ưu tiên Constructor Injection.
Tóm tắt
BeanFactorylà giao diện tra cứu tối thiểu, tải lười (Lazy) mặc định theo yêu cầu.ApplicationContextbọc mộtDefaultListableBeanFactory(Composition) và nạp sớm (Eager) mọi Singleton bean khi khởi động.- Bản chất Singleton trong Spring là một entry trong bảng băm
ConcurrentHashMapmang tênsingletonObjects. - Việc tách riêng siêu dữ liệu
beanDefinitionMapcho phép can thiệp tinh chỉnh cấu hình trước khi đối tượng thật được cấp phát.
Tự kiểm tra
- Q1Tại sao ApplicationContext lại khởi tạo trước (Eager Loading) toàn bộ bean Singleton lúc khởi động thay vì Lazy như BeanFactory?
- Q2Mối quan hệ cài đặt giữa ApplicationContext và BeanFactory trong mã nguồn Spring là gì?
- Q3Điều gì xảy ra nếu bạn khởi tạo hai instance AnnotationConfigApplicationContext từ cùng một file AppConfig trong cùng một ứng dụng?
- Q4Tại sao DefaultListableBeanFactory lại tách riêng beanDefinitionMap và singletonObjects thành hai map khác nhau?
- Q5Cấu trúc dữ liệu nào được sử dụng cho singletonObjects trong Spring và tại sao?
Bài tiếp theo: Vòng Đời Khởi Động: Giải Mã 12 Bước Trong Phương Thức refresh()
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Container Internals: Mổ Xẻ Bên Dưới Spring Container
Bài tiếpVòng Đời Khởi Động: Giải Mã 12 Bước Trong refresh()
Ôn phỏng vấn
Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.
Thảo luận & góp ý
Hỏi đáp về bài này