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

BeanDefinition & BFPP: Thao Tác Siêu Dữ Liệu Trước Khi Tạo Bean

Mổ xẻ BeanDefinition, phân biệt BeanFactoryPostProcessor với BeanPostProcessor và lý do tách pha metadata làm nền tảng cho Spring autoconfig.

TL;DR: Khi bạn khai báo @Component hay @Bean, Spring Container chưa khởi tạo đối tượng Java ngay lập tức. Thay vào đó, container ghi nhận một bản mô tả siêu dữ liệu mang tên BeanDefinition (lưu trữ class, scope, dependency) vào bảng băm beanDefinitionMap. Thực thể đối tượng chỉ được tạo ra ở bước 11 của refresh(). Trước thời điểm đó, BeanFactoryPostProcessor (BFPP) được phép đọc và biến đổi bản vẽ siêu dữ liệu này. Phân biệt rõ: BFPP can thiệp vào bản vẽ (metadata) trước khi tạo bean, còn BeanPostProcessor (BPP) can thiệp vào instance thật sau khi đối tượng đã ra đời.

Trong bài Vòng Đời Khởi Động: Giải Mã 12 Bước Trong Phương Thức refresh(), chúng ta đã biết giai đoạn 2 (bước 5–6) là cửa sổ duy nhất để tinh chỉnh container. Bài viết này sẽ mổ xẻ cấu trúc của BeanDefinition và cơ chế mà các BFPP can thiệp vào quá trình khởi tạo ứng dụng.

Hiện tượng cấu hình bean bị biến đổi bất thường bắt nguồn từ đâu?

Xét một tình huống thực tế: Bạn khai báo @Service trên lớp ReportService với scope mặc định là Singleton. Tuy nhiên, khi kiểm tra bằng thực nghiệm, mỗi lần gọi getBean("reportService") container lại trả về một instance hoàn toàn mới (hành vi giống hệt Prototype), dù trong mã nguồn không ai chỉnh sửa chú thích.

Nguyên nhân: Một BeanFactoryPostProcessor trong thư viện phụ trợ đã can thiệp vào beanDefinitionMap tại bước 5 của refresh() và gọi phương thức:

Java
// Can thiep sua scope truc tiep tren ban ve thiet ke
def.setScope("prototype");

Đây là cơ chế hoàn toàn hợp lệ của Spring, chứng minh rằng: Các chú thích Java chỉ là dữ liệu đầu vào ban đầu, còn hành vi thực tế tại runtime phụ thuộc vào trạng thái cuối cùng của BeanDefinition sau khi đi qua chuỗi BFPP.

BeanDefinition đại diện cho những thông tin siêu dữ liệu nào?

BeanDefinition là một giao diện cốt lõi đóng gói toàn bộ thông số kỹ thuật mà container cần để tạo lập đối tượng:

Java
// Minh hoa cac truong thong tin co ban trong BeanDefinition
BeanDefinition def = new RootBeanDefinition();
def.setBeanClassName("com.olhub.OrderService");
def.setScope("singleton");
def.setLazyInit(false);
def.getConstructorArgumentValues()
   .addGenericArgumentValue(new RuntimeBeanReference("paymentGateway"));

Các thuộc tính chủ chốt trong BeanDefinition:

Thuộc tínhPhương thức truy xuấtMô tả ý nghĩa
Class NamegetBeanClassName()Tên đầy đủ (FQN) của class sẽ được Reflection nạp
ScopegetScope()Phạm vi vòng đời: "singleton", "prototype", "request"
Lazy InitisLazyInit()Trì hoãn tạo đối tượng cho tới khi có request đầu tiên
DependenciesgetDependsOn()Danh sách bean bắt buộc phải khởi tạo trước
Init / DestroygetInitMethodName()Tên hàm khởi tạo và hàm dọn dẹp tài nguyên
RolegetRole()Phân loại: ROLE_APPLICATION (nghiệp vụ) hay ROLE_INFRASTRUCTURE (nội bộ Spring)

Vòng đời một BeanDefinition đi từ scan xuống beanDefinitionMap, qua BeanFactoryPostProcessor còn sửa được định nghĩa, tới bước 11 tạo object thật rồi vào singletonObjects; chú thích bên phải đánh dấu mốc sau đó chỉ còn object nên phải dùng BeanPostProcessor

BeanFactoryPostProcessor can thiệp vào siêu dữ liệu trước khi tạo bean ra sao?

BeanFactoryPostProcessor là một Functional Interface đơn giản:

Java
@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
        throws BeansException;
}

Nhờ nhận tham số ConfigurableListableBeanFactory, BFPP có thể duyệt qua toàn bộ beanDefinitionMap và tinh chỉnh cấu hình:

Java
@Component
public class ForceLazyBFPP implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
        for (String name : bf.getBeanDefinitionNames()) {
            BeanDefinition def = bf.getBeanDefinition(name);
            // Chi can thiep bean nghiep vu cua ung dung
            if (def.getRole() == BeanDefinition.ROLE_APPLICATION) {
                def.setLazyInit(true); // Ep moi bean ung dung thanh lazy
            }
        }
    }
}

Các BFPP nòng cốt trong Spring Framework:

  • ConfigurationClassPostProcessor: Chạy đầu tiên ở bước 5, quét toàn bộ class @Configuration, phân tích các phương thức @Bean và sinh thêm BeanDefinition tương ứng vào map.
  • PropertySourcesPlaceholderConfigurer: Quét các chuỗi ${...} trong @Value trên BeanDefinition và thay thế bằng giá trị thực tế lấy từ môi trường (Environment).

Phân biệt bản chất giữa BeanFactoryPostProcessor và BeanPostProcessor?

Mặc dù có tên gọi khá tương đồng, hai interface này hoạt động ở hai giai đoạn hoàn toàn khác biệt:

Tiêu chíBeanFactoryPostProcessor (BFPP)BeanPostProcessor (BPP)
Đối tượng can thiệpBeanDefinition (Bản vẽ siêu dữ liệu)Bean Instance (Thực thể đối tượng trên Heap)
Thời điểm chạyBước 5 (Trước khi tạo bất kỳ bean nào)Bước 11 (Quanh thời điểm tạo từng bean)
Tham số truyền vàoConfigurableListableBeanFactoryObject instance + Bean Name
Khả năng thay thế đối tượngKhông (Chỉ sửa metadata)Có (Bọc đối tượng bằng Dynamic Proxy)
Ví dụ điển hìnhConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessor, AspectJAwareAdvisorAutoProxyCreator

Tại sao việc tách biệt pha Metadata khỏi pha Instance là nền tảng của Autoconfig?

Nếu Spring tạo đối tượng ngay khi vừa quét thấy @Component trong classpath, framework sẽ không có cơ hội điều chỉnh hành vi nếu thiếu cấu hình hoặc thư viện bên ngoài.

Nhờ việc tách biệt quy trình làm 2 pha:

  1. Pha 1 (Thu thập Metadata): Toàn bộ cấu hình được số hóa thành các đối tượng BeanDefinition.
  2. Pha 2 (Biến đổi Metadata qua BFPP): Các starter của Spring Boot kích hoạt điều kiện @ConditionalOnClass, @ConditionalOnMissingBean để thêm bớt hoặc ghi đè BeanDefinition trước khi bước vào cấp phát bộ nhớ.

Nhờ đó, Spring Boot có thể tự động cấu hình HikariDataSource hay SecurityFilterChain mà không bắt buộc lập trình viên phải sửa đổi mã nguồn.

Bảng tra cứu lỗi nhanh liên quan đến BeanDefinition và PostProcessor?

Ngoại lệ thường gặpBước phát sinhNguyên nhân cốt lõi
BeanDefinitionParsingExceptionBước 2 hoặc 5Cú pháp class @Configuration sai (ví dụ đánh dấu final)
BeanDefinitionStoreExceptionBước 5Không thể giải mã biểu thức ${...} trong file cấu hình
BeanDefinitionOverrideExceptionBước 5Hai bean bị trùng tên (bị Spring Boot cấm mặc định)
BeanCurrentlyInCreationException trong BFPPBước 5Anti-pattern: Gọi bf.getBean() trong phương thức của BFPP
Behavior thực tế không khớp annotationSau bước 5Một BFPP tùy biến đã ghi đè thuộc tính trong BeanDefinition

Tóm tắt

  • BeanDefinition là bản vẽ siêu dữ liệu trong beanDefinitionMap, hoàn toàn chưa phải là instance đối tượng trên heap.
  • BeanFactoryPostProcessor chạy ở bước 5 của refresh() và can thiệp trực tiếp vào bản vẽ siêu dữ liệu.
  • BeanPostProcessor chạy ở bước 11 để tiêm phụ thuộc @Autowired và bọc AOP Dynamic Proxy cho instance.
  • Tách riêng siêu dữ liệu cho phép Spring Boot kiểm tra điều kiện và cấu hình tự động linh hoạt trước khi cấp phát bộ nhớ.

Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Một lớp mang chú thích @Component được quét thấy ở bước 2 của refresh(). Tại thời điểm này, đối tượng Java thực tế đã được tạo ra trong heap chưa?
  2. Q2
    Điều gì xảy ra nếu bạn loại bỏ ConfigurationClassPostProcessor khỏi chu kỳ khởi động của Spring Container?
  3. Q3
    Khác biệt quyết định giữa BeanFactoryPostProcessor và BeanPostProcessor là gì?
  4. Q4
    Tại sao việc tách rời pha Metadata (BeanDefinition) khỏi pha Instance lại là yếu tố sống còn cho Spring Boot Auto-configuration?
  5. Q5
    Khi gặp lỗi BeanDefinitionOverrideException lúc khởi động, nguyên nhân kỹ thuật phổ biến nhất là gì?

Bài tiếp theo: Environment & PropertySource: Cơ Chế Cấu Hình Đa Nguồn

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

Environment & PropertySource: Cơ Chế Cấu Hình Đa Nguồn