Spring Core & Boot
16/41
Bài 16 / 41~12 phútBean Lifecycle & ScopesLộ trình · chặng 13/21Miễn phí… lượt xem

Vòng Đời Của Bean: 9 Giai Đoạn Từ Khởi Tạo Đến Hủy Bỏ

Chi tiết 9 giai đoạn vòng đời bean trong Spring: Constructor, Populate, Aware, BPP before, @PostConstruct, AOP proxy wrap đến @PreDestroy.

TL;DR: Mỗi bean trong Spring trải qua 9 giai đoạn vòng đời cố định: Cấp phát Constructor -> Tiêm thuộc tính @Autowired -> Kích hoạt *Aware callbacks -> Chạy BPP before-init -> Chạy @PostConstruct -> Chạy InitializingBean.afterPropertiesSet -> Gọi init-method tùy biến -> Chạy BPP after-init (nơi bọc Dynamic Proxy) -> Sẵn sàng hoạt động, và giải phóng qua @PreDestroy khi container dừng. Thứ tự này không thể đảo ngược vì giai đoạn sau luôn đòi hỏi kết quả hoàn tất của giai đoạn trước.

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 Bước 11 đảm nhiệm việc khởi tạo toàn bộ Singleton bean. Bài viết này sẽ phóng to vào từng bean riêng biệt: Một instance trải qua 9 giai đoạn cụ thể nào từ khi được cấp phát bộ nhớ tới lúc bị hủy bỏ?

Sơ đồ tổng quát 9 giai đoạn vòng đời của Bean trông như thế nào?

Mỗi bean trong Spring Container di chuyển tuần tự qua 9 nấc thang vòng đời:

Chín giai đoạn của một bean xếp dọc từ instantiate tới destroy; chú thích bên phải đánh dấu ba chỗ có hệ quả: dependency chỉ tồn tại từ bước 2, ba hook init ở bước 5 đến 7 chạy theo thứ tự cố định, và bước 8 là nơi AOP bọc proxy

Giai đoạnThao tác thực thiChú thích / InterfaceVai trò cốt lõi
1. InstantiateGọi ConstructorReflection newCấp phát bộ nhớ cho instance
2. PopulateTiêm Dependency@Autowired, SetterTiêm các thuộc tính phụ thuộc vào bean
3. AwareNhận diện ngữ cảnhBeanNameAware, BeanFactoryAwareCung cấp metadata nội bộ của container
4. BPP BeforeTiền khởi tạopostProcessBeforeInitializationHook can thiệp trước khi init logic chạy
5. PostConstructKhởi tạo chuẩn@PostConstruct (JSR-250)Logic khởi tạo nghiệp vụ chính
6. InitializingKhởi tạo Spring cũInitializingBean.afterPropertiesSetKế thừa từ Spring 1.x
7. Custom InitKhởi tạo ngoài@Bean(initMethod = "...")Dành cho thư viện bên ngoài
8. BPP AfterHậu khởi tạopostProcessAfterInitializationBọc AOP Dynamic Proxy tại đây
9. DestroyDọn dẹp tài nguyên@PreDestroy, DisposableBeanĐóng kết nối khi container shutdown

Tại sao Constructor luôn chạy trước Populate và dễ gây NullPointerException?

Đây là một trong những cạm bẫy kinh điển nhất của lập trình viên Spring:

Java
@Service
public class OrderService {
    @Autowired private PaymentGateway payment; // Field injection

    public OrderService() {
        // Giai doan 1: Constructor chay
        // payment van la NULL vi buoc 2 chua dien ra!
        payment.initConnection(); // Nem NullPointerException
    }
}

Theo quy tắc của JVM, máy ảo bắt buộc phải cấp phát bộ nhớ và gọi constructor xong thì mới có đối tượng để Spring tiêm trường dữ liệu vào. Vì vậy:

  1. Giai đoạn 1 (Instantiate): Chạy constructor.
  2. Giai đoạn 2 (Populate): Spring mới dùng Reflection gán giá trị cho trường @Autowired.

Nếu bạn muốn thực thi logic ngay khi khởi tạo, hãy sử dụng Constructor Injection (dependency có sẵn ngay lập tức) hoặc chuyển logic vào @PostConstruct.

Các callback nhóm Aware giúp Bean nhận biết ngữ cảnh ra sao?

Nhóm interface *Aware cho phép bean nhận biết thông tin hạ tầng mà container đang quản lý:

  • BeanNameAware: Nhận tên định danh của chính bean trong context qua setBeanName(String).
  • BeanFactoryAware: Nhận tham chiếu tới BeanFactory quản lý.
  • ApplicationContextAware: Nhận tham chiếu tới ApplicationContext qua bộ xử lý ApplicationContextAwareProcessor tại giai đoạn BPP Before.

Trong mã nguồn nghiệp vụ thông thường, bạn không nên triển khai các interface này mà hãy tiêm trực tiếp ApplicationContext qua constructor nếu thực sự cần thiết.

Thứ tự thực thi giữa @PostConstruct, afterPropertiesSet và init-method?

Nếu một bean cấu hình cả 3 cơ chế khởi tạo, Spring sẽ kích hoạt theo thứ tự cố định:

Java
@Component
public class LifecycleDemoBean implements InitializingBean {
    @PostConstruct
    public void postConstruct() {
        // 1. Chay dau tien: Chuan JSR-250 (Khuyen nghi nhat)
    }

    @Override
    public void afterPropertiesSet() {
        // 2. Chay thu hai: Interface Spring cu
    }

    public void customInit() {
        // 3. Chay cuoi cung: Khai bao qua @Bean(initMethod = "customInit")
    }
}

Nguyên tắc bất biến: Tất cả các hàm init chỉ được gọi sau khi mọi dependency đã được tiêm đầy đủ. Bất kỳ ngoại lệ nào phát sinh trong @PostConstruct sẽ khiến ứng dụng dừng ngay lập tức (Fail-Fast), ngăn chặn việc hệ thống hoạt động với cấu hình lỗi.

Dynamic Proxy của AOP được bọc ở giai đoạn nào trong vòng đời?

Giai đoạn 8 (BeanPostProcessor.postProcessAfterInitialization) là nơi AnnotationAwareAspectJAutoProxyCreator kiểm tra xem bean có chứa các chú thích AOP như @Transactional, @Async, hay @Secured hay không.

Nếu có, Spring sẽ không trả về instance nguyên bản mà tạo ra một Dynamic Proxy bọc quanh đối tượng. Do proxy được bọc ở giai đoạn cuối cùng sau khi @PostConstruct đã chạy xong, các phương thức tự gọi nội bộ (this.method()) trong cùng một class sẽ không đi qua proxy và không kích hoạt transaction.

Giai đoạn Destroy và callback @PreDestroy được kích hoạt khi nào?

Khi ứng dụng nhận tín hiệu tắt (SIGTERM), Spring kích hoạt chu trình hủy theo thứ tự ngược lại với chu trình tạo lập (bean phụ thuộc bị hủy sau bean sử dụng nó):

Java
@Service
public class ConnectionPoolManager {
    @PreDestroy
    public void cleanUp() {
        // Dong ket noi database an toan truoc khi JVM thoat
        pool.close();
    }
}

Các lưu ý quan trọng về giai đoạn Destroy:

  • Graceful Shutdown: Bật cấu hình server.shutdown: graceful trong Spring Boot để máy chủ chờ các HTTP request đang xử lý hoàn tất trước khi kích hoạt @PreDestroy.
  • Lệnh SIGKILL (kill -9): Tiêu diệt tiến trình ngay lập tức ở mức OS, bỏ qua toàn bộ callback dọn dẹp.
  • Prototype Bean: Spring không quản lý vòng đời hủy của các bean Prototype. Đối tượng prototype sau khi tạo xong sẽ được giao toàn quyền cho caller tự quản lý và dọn dẹp.

Tóm tắt

  • Vòng đời bean gồm 9 giai đoạn từ khởi tạo bộ nhớ, tiêm thuộc tính, chạy callback init, bọc proxy tới khi hủy bỏ.
  • Không truy cập trường @Autowired trong constructor vì bước populate chỉ diễn ra sau khi constructor kết thúc.
  • Ba cơ chế khởi tạo chạy tuần tự: @PostConstruct -> afterPropertiesSet -> init-method.
  • AOP Dynamic Proxy được bọc ở bước 8 sau khi toàn bộ logic khởi tạo đã hoàn tất.

Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tại sao việc gọi phương thức trên một trường được gắn @Autowired bên trong constructor lại ném ngoại lệ NullPointerException?
  2. Q2
    Nếu một bean sử dụng cả 3 cơ chế khởi tạo: @PostConstruct, InitializingBean, và initMethod trong @Bean, thứ tự thực thi sẽ ra sao?
  3. Q3
    Dynamic Proxy của AOP (@Transactional, @Async) được tạo ra và bọc quanh Bean ở giai đoạn nào trong vòng đời?
  4. Q4
    Điều gì xảy ra với các callback @PreDestroy trên một Bean được cấu hình phạm vi Prototype?
  5. Q5
    Khi một ngoại lệ không kiểm soát (Uncaught Exception) phát sinh bên trong phương thức @PostConstruct, ứng dụng sẽ phản ứng thế nào?

Bài tiếp theo: Cơ Chế AOP Dynamic Proxy: So Sánh JDK Proxy Và CGLIB

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.

Lộ trình Junior Java Backend Developer

Chặng 13/21 · Spring IoC, DI & Bean — bài 8/17 của chặng

Ô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

Cơ Chế AOP Dynamic Proxy: So Sánh JDK Proxy Và CGLIB