Spring Core & Boot
8/41
Bài 8 / 41~12 phútContainer InternalsMiễn phí… lượt xem

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:

Java
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:

Bên trong DefaultListableBeanFactory có hai map tách rời: beanDefinitionMap chỉ chứa metadata nên chưa có object nào, còn singletonObjects mới chứa instance thật, và createBean ở bước 11 của refresh là cầu nối giữa hai map

  1. 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.
  2. 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:

Luồng getBean rẽ hai nhánh tại câu hỏi có entry trong singletonObjects hay chưa: nhánh có thì trả instance đã cache ngay, nhánh chưa phải chạy createBean rồi đặt vào map trước khi trả về

  • 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ọi getBean() để 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:

Java
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.

Bên trong AnnotationConfigApplicationContext có bốn capability cộng thêm và một field beanFactory kiểu DefaultListableBeanFactory; lời gọi ctx.getBean đi thẳng xuống field đó chứ không đi qua nhóm capability

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ộngInterface tương ứngỨng dụng thực tế
Environment & PropertiesEnvironmentCapableĐọc cấu hình @Value("${...}"), kích hoạt Profile
Đa ngôn ngữ (i18n)MessageSourceNạp file thông báo messages.properties
Phát sự kiện (Event Bus)ApplicationEventPublisherBắn và lắng nghe sự kiện nội bộ qua @EventListener
Nạp tài nguyên thống nhấtResourcePatternResolverĐọ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ẽ:

Plain Text
AnnotationConfigApplicationContext
 └─ beanFactory: DefaultListableBeanFactory
     ├─ beanDefinitionMap     : LinkedHashMap<String, BeanDefinition>
     ├─ singletonObjects      : ConcurrentHashMap<String, Object>
     ├─ earlySingletonObjects : ConcurrentHashMap<String, Object>
     └─ singletonFactories    : HashMap<String, ObjectFactory<?>>
  • singletonObjects là 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).
  • beanDefinitionMap là 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?

  1. Khởi tạo 2 Context trong cùng một JVM: Nếu bạn tạo ctx1 và ctx2 từ cùng một cấu hình, mỗi context sẽ sở hữu một DefaultListableBeanFactory độc lập. Bean singleton ở ctx1 và ctx2 là hai thực thể hoàn toàn khác nhau trên bộ nhớ, không hề chia sẻ trạng thái.
  2. 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.
  3. 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

  • BeanFactory là giao diện tra cứu tối thiểu, tải lười (Lazy) mặc định theo yêu cầu.
  • ApplicationContext bọc một DefaultListableBeanFactory (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 ConcurrentHashMap mang tên singletonObjects.
  • Việc tách riêng siêu dữ liệu beanDefinitionMap cho 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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tạ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?
  2. Q2
    Mối quan hệ cài đặt giữa ApplicationContext và BeanFactory trong mã nguồn Spring là gì?
  3. 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?
  4. Q4
    Tại sao DefaultListableBeanFactory lại tách riêng beanDefinitionMap và singletonObjects thành hai map khác nhau?
  5. Q5
    Cấ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.

Ô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

Bài tiếp theo

Vòng Đời Khởi Động: Giải Mã 12 Bước Trong refresh()