Skip to content

Spring Bean Lifecycle

9 min read

Spring Bean Lifecycle là vòng đời của một bean từ lúc Spring tạo đối tượng, inject dependency, gọi các callback khởi tạo, sử dụng bean cho đến khi container đóng và bean được hủy.

1. Tóm tắt nhanh#

Một bean thông thường trong Spring đi qua các bước chính:

  1. Instantiate – Spring tạo instance của bean.
  2. Populate properties – Spring inject dependency và thiết lập các thuộc tính.
  3. Aware callbacks – gọi các interface *Aware phù hợp để bean nhận thông tin từ container.
  4. BeanPostProcessor trước khởi tạo – postProcessBeforeInitialization.
  5. Initialization callbacks – chạy @PostConstruct, InitializingBean.afterPropertiesSet() và/hoặc initMethod đã cấu hình.
  6. BeanPostProcessor sau khởi tạo – postProcessAfterInitialization; tại đây bean có thể được bọc bằng proxy.
  7. Ready for use – bean được đưa vào sử dụng.
  8. Destruction callbacks – khi container đóng, Spring gọi các callback hủy như @PreDestroy, DisposableBean.destroy() và/hoặc destroyMethod.

Sơ đồ tổng quan#

flowchart TD
    A[Bean definition được đăng ký] --> B[Instantiate: tạo instance]
    B --> C[Populate properties: inject dependency]
    C --> D[Aware callbacks]
    D --> E[BeanPostProcessor<br/>postProcessBeforeInitialization]
    E --> F[Initialization callbacks]
    F --> G[BeanPostProcessor<br/>postProcessAfterInitialization]
    G --> H[Bean sẵn sàng sử dụng]
    H --> I{ApplicationContext đóng?}
    I -- Chưa --> H
    I -- Rồi --> J[Destruction callbacks]
    J --> K[Bean được hủy]

Ghi nhớ: BeanPostProcessor có thể can thiệp vào bean trước và sau giai đoạn khởi tạo. Nhiều tính năng Spring, chẳng hạn xử lý annotation và tạo proxy, dựa vào các extension point này.


2. Bean là gì?#

Trong Spring, bean là một đối tượng được Spring IoC container khởi tạo, cấu hình và quản lý. Thay vì tự tạo và quản lý toàn bộ dependency bằng new, ứng dụng khai báo bean thông qua annotation, Java configuration hoặc XML; Spring chịu trách nhiệm kết nối chúng.

Ví dụ:

import org.springframework.stereotype.Service;

@Service
public class GreetingService {
    public String greet() {
        return "Hello Spring";
    }
}

Với component scanning được bật, Spring phát hiện GreetingService và đăng ký nó làm bean.


3. Các giai đoạn trong vòng đời#

3.1. Đọc và đăng ký BeanDefinition#

Trước khi tạo instance, Spring đọc cấu hình và xây dựng metadata mô tả bean, thường gọi là BeanDefinition. Metadata có thể chứa:

  • Class cần khởi tạo.
  • Scope (singleton, prototype, …).
  • Constructor arguments và dependency.
  • Các phương thức init/destroy.
  • Thông tin cấu hình khác.

BeanDefinition không phải là instance của bean; nó là bản mô tả để container biết cần tạo và quản lý đối tượng như thế nào.

3.2. Instantiate – tạo đối tượng#

Spring tạo instance từ BeanDefinition, thường thông qua constructor hoặc factory method.

@Component
public class OrderService {
    public OrderService() {
        System.out.println("Constructor called");
    }
}

Ở bước này, đối tượng đã tồn tại nhưng dependency và các thuộc tính được inject có thể chưa được thiết lập đầy đủ. Vì vậy, không nên dựa vào dependency được inject trong constructor của chính bean theo cách không phù hợp với cơ chế khởi tạo.

3.3. Populate properties – inject dependency#

Sau khi tạo instance, Spring giải quyết dependency và gán chúng vào bean. Dependency injection có thể thực hiện qua constructor, field hoặc setter; constructor injection thường giúp thể hiện rõ dependency bắt buộc.

@Repository
public class OrderRepository {
    public void save() {
        // Persist order
    }
}

@Service
public class OrderService {
    private final OrderRepository repository;

    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
}

Với constructor injection, dependency được cung cấp ngay khi tạo đối tượng. Trong lifecycle tổng quát, bước thiết lập dependency diễn ra trước các callback khởi tạo.

3.4. Aware callbacks – nhận thông tin từ Spring#

Nếu bean triển khai các interface Aware, Spring có thể gọi callback tương ứng để cung cấp thông tin về container hoặc môi trường.

Một số interface thường gặp:

Interface Thông tin được cung cấp


BeanNameAware Tên bean trong container BeanFactoryAware BeanFactory đang quản lý bean ApplicationContextAware ApplicationContext EnvironmentAware Environment

Ví dụ:

@Component
public class ContextAwareBean implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext context) {
        System.out.println("ApplicationContext received");
    }
}

Nên ưu tiên dependency injection thông thường thay vì phụ thuộc vào ApplicationContextAware nếu chỉ cần một service hoặc repository cụ thể. Cách đó thường làm code dễ kiểm thử và ít phụ thuộc container hơn.

3.5. BeanPostProcessor trước khởi tạo#

Spring gọi postProcessBeforeInitialization() của các BeanPostProcessor đã đăng ký, trước các callback init thông thường.

@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("Before init: " + beanName);
        return bean;
    }
}

Đây là extension point cấp container: processor có thể kiểm tra, sửa đổi hoặc trả về một đối tượng thay thế. Nếu processor trả về null, chuỗi xử lý tiếp theo có thể bị dừng đối với bean đó.

3.6. Initialization callbacks – khởi tạo bean#

Sau giai đoạn trước-init, Spring gọi các callback khởi tạo mà bean đã khai báo. Những cách phổ biến gồm:

  1. @PostConstruct
  2. InitializingBean.afterPropertiesSet()
  3. Phương thức init tùy chỉnh, ví dụ @Bean(initMethod = "init")

Ví dụ kết hợp:

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class CacheService implements InitializingBean {

    @PostConstruct
    public void postConstruct() {
        System.out.println("@PostConstruct");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("afterPropertiesSet");
    }

    public void init() {
        System.out.println("custom init method");
    }
}

Cấu hình phương thức init tùy chỉnh:

@Configuration
public class AppConfig {
    @Bean(initMethod = "init")
    public CacheService cacheService() {
        return new CacheService();
    }
}

Nếu một bean khai báo nhiều cơ chế, chúng có thể cùng được gọi theo thứ tự lifecycle mà Spring quy định. Trong ứng dụng thực tế, nên chọn cách phù hợp và tránh lặp cùng một công việc ở nhiều callback.

Dùng init callback khi nào? Khi cần kiểm tra cấu hình, chuẩn bị tài nguyên hoặc xây dựng trạng thái nội bộ sau khi dependency đã được thiết lập. Không nên dùng callback khởi tạo để thực hiện tác vụ kéo dài hoặc phụ thuộc vào toàn bộ ứng dụng đã sẵn sàng nếu lifecycle đó chưa bảo đảm điều kiện.

3.7. BeanPostProcessor sau khởi tạo#

Sau các callback init, Spring gọi postProcessAfterInitialization().

@Component
public class AfterInitProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("After init: " + beanName);
        return bean;
    }
}

Một processor có thể trả về proxy thay cho bean ban đầu. Đây là một trong các cơ chế nền tảng hỗ trợ những tính năng như AOP, transaction management và một số hình thức xử lý annotation.

Điều này cũng giải thích vì sao đối tượng mà code nhận được đôi khi là proxy chứ không phải instance lớp triển khai ban đầu.

3.8. Ready for use – bean hoạt động#

Sau khi hoàn tất khởi tạo và các processor, bean được xem là sẵn sàng để container cung cấp cho các thành phần khác. Với bean singleton, Spring thường quản lý một instance dùng chung trong container.

Lưu ý: “bean đã khởi tạo” không đồng nghĩa với “toàn bộ ứng dụng đã sẵn sàng nhận traffic”. Nếu công việc cần chạy sau khi context đã refresh hoặc ứng dụng đã sẵn sàng, hãy cân nhắc các cơ chế như ApplicationReadyEvent trong Spring Boot thay vì đặt mọi thứ vào @PostConstruct.

3.9. Destruction callbacks – hủy bean#

Khi ApplicationContext đóng, Spring gọi các callback hủy được cấu hình cho những bean mà container quản lý vòng đời. Các cơ chế thường gặp:

  • @PreDestroy
  • DisposableBean.destroy()
  • Phương thức hủy tùy chỉnh, ví dụ @Bean(destroyMethod = "close")

Ví dụ:

import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class ConnectionManager {

    @PostConstruct
    public void start() {
        System.out.println("Acquire resources");
    }

    @PreDestroy
    public void stop() {
        System.out.println("Release resources");
    }
}

Với Java configuration:

@Bean(destroyMethod = "close")
public SomeClient someClient() {
    return new SomeClient();
}

Callback hủy phù hợp để giải phóng tài nguyên như connection, thread hoặc client có phương thức đóng. Không nên giả định callback luôn chạy khi tiến trình bị kill đột ngột, mất điện hoặc JVM dừng bất thường.


4. Thứ tự callback thường gặp#

Với một bean thông thường, trình tự khái quát là:

Constructor / factory method
        ↓
Dependency injection
        ↓
Aware callbacks
        ↓
BeanPostProcessor.postProcessBeforeInitialization
        ↓
@PostConstruct
        ↓
InitializingBean.afterPropertiesSet
        ↓
Custom init method
        ↓
BeanPostProcessor.postProcessAfterInitialization
        ↓
Bean ready for use
        ...
ApplicationContext shutdown
        ↓
@PreDestroy
        ↓
DisposableBean.destroy
        ↓
Custom destroy method

Đây là sơ đồ học tập cho lifecycle phổ biến, không phải mô tả mọi nhánh nội bộ. Thứ tự cụ thể có thể chịu ảnh hưởng của loại bean, scope, cấu hình và phiên bản Spring; các processor cũng có thể làm thay đổi đối tượng được trả về.


5. Bean scope ảnh hưởng thế nào đến lifecycle?#


Scope Đặc điểm Hủy bean


singleton Một instance cho mỗi Container quản lý Spring container callback hủy

prototype Tạo instance mới mỗi Spring không quản lý lần được yêu cầu từ đầy đủ callback hủy sau container khi giao bean cho client

request Một instance trong phạm Hủy khi request scope vi HTTP request kết thúc

session Một instance trong phạm Hủy khi session scope vi HTTP session kết thúc

application Một instance trong phạm Theo vòng đời vi ServletContext application context tương ứng#

Với prototype, ứng dụng cần tự giải phóng tài nguyên nếu bean giữ tài nguyên cần đóng. Đừng dựa vào @PreDestroy để Spring tự gọi cho mọi prototype instance.


6. Ví dụ kiểm tra lifecycle bằng log#

Có thể tạo một bean đơn giản để quan sát thứ tự callback:

@Component
public class LifecycleDemo implements
        BeanNameAware,
        InitializingBean,
        DisposableBean {

    @Override
    public void setBeanName(String name) {
        System.out.println("1. BeanNameAware: " + name);
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("2. @PostConstruct");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("3. afterPropertiesSet");
    }

    public void customInit() {
        System.out.println("4. custom init");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("5. @PreDestroy");
    }

    @Override
    public void destroy() {
        System.out.println("6. DisposableBean.destroy");
    }
}

Để quan sát custom init, khai báo bean bằng @Bean(initMethod = "customInit") thay vì chỉ dựa vào @Component:

@Configuration
public class LifecycleConfig {
    @Bean(initMethod = "customInit")
    public LifecycleDemo lifecycleDemo() {
        return new LifecycleDemo();
    }
}

Không khai báo đồng thời cùng một bean qua component scanning và @Bean theo cách tạo hai bean riêng nếu mục tiêu chỉ là quan sát một lifecycle.


7. Những lưu ý thường gặp khi phỏng vấn và làm dự án#

  • Constructor chạy trước @PostConstruct: constructor tạo đối tượng; @PostConstruct chạy sau khi Spring đã thiết lập dependency và trước khi bean được đưa vào sử dụng.
  • @PostConstruct không phải sự kiện ứng dụng sẵn sàng: nó gắn với khởi tạo từng bean, không bảo đảm mọi bean hoặc mọi tác vụ startup khác đã hoàn tất.
  • BeanPostProcessor không giống BeanFactoryPostProcessor: BeanFactoryPostProcessor làm việc với metadata/định nghĩa bean trước khi các bean thông thường được khởi tạo; BeanPostProcessor xử lý instance bean trong quá trình tạo.
  • AOP có thể tạo proxy: vì vậy lời gọi qua proxy có thể khác lời gọi trực tiếp trên đối tượng; các vấn đề như self-invocation trong transaction/AOP cần được hiểu riêng.
  • Prototype không có cleanup tự động đầy đủ: caller chịu trách nhiệm với tài nguyên của prototype sau khi nhận instance.
  • Shutdown hook không bảo đảm trong mọi tình huống: callback hủy phụ thuộc việc container được đóng đúng cách.

8. Kết luận#

Có thể nhớ Spring Bean Lifecycle theo chuỗi:

Tạo bean → inject dependency → Aware → before-init processor → init callbacks → after-init processor → sử dụng → destroy callbacks.

Nắm được vòng đời này giúp hiểu cách Spring quản lý dependency, nơi đặt logic khởi tạo/giải phóng tài nguyên, và vì sao các tính năng như AOP hoặc transaction có thể can thiệp vào bean.