Spring Boot/Bean lifecycle — 9 giai đoạn từ instantiate đến destroy
~26 phútSpring là gì & nền tảng IoCMiễn phí

Bean lifecycle — 9 giai đoạn từ instantiate đến destroy

Mọi bean Spring đều đi qua 9 giai đoạn lifecycle có thứ tự cố định. Bài này map từng giai đoạn vào callback annotation cụ thể (@PostConstruct, InitializingBean, BPP), bóc tách *Aware interfaces, JDK vs CGLIB proxy, SmartLifecycle, graceful shutdown production, và pitfall classic về self-call @Transactional.

Bài 03 đã chỉ ra refresh() có 12 bước, trong đó bước 11 instantiate tất cả singleton. Câu hỏi phóng to: trong bước 11, mỗi bean cụ thể đi qua giai đoạn nào? Khi bạn ghi @PostConstruct, đoạn code đó chạy lúc nào — trước hay sau khi @Autowired field đã set?

Bài này bóc tách chính xác 9 giai đoạn lifecycle của một bean. Có thứ tự cố định, không thể thay đổi. Hiểu rồi, bạn debug được những bug "tại sao field tôi inject lại null trong constructor", "tại sao destroy callback không chạy", "tại sao bean của tôi bị wrap proxy".

1. 9 giai đoạn — sơ đồ tổng

flowchart TB
    A["1. Instantiate<br/>(call constructor)"]
    B["2. Populate properties<br/>(@Autowired field/setter)"]
    C["3. *Aware callbacks<br/>(BeanNameAware, ApplicationContextAware)"]
    D["4. BeanPostProcessor<br/>postProcessBeforeInitialization"]
    E["5. @PostConstruct"]
    F["6. InitializingBean.afterPropertiesSet()"]
    G["7. Custom init-method"]
    H["8. BeanPostProcessor<br/>postProcessAfterInitialization"]
    I["Bean ready - phuc vu request"]
    J["9. @PreDestroy<br/>+ DisposableBean.destroy()<br/>+ custom destroy-method"]

    A --> B --> C --> D --> E --> F --> G --> H --> I --> J

    style E fill:#d1fae5
    style H fill:#fef3c7
    style J fill:#fee

Bảng tóm tắt từng bước:

#Giai đoạnCode/annotationKhi nào dùng
1InstantiateconstructorBean class được new với DI qua constructor
2Populate@Autowired field/setterField/setter inject cho dep không qua constructor
3*Awareimplement BeanNameAware, ApplicationContextAware, ...Bean cần biết tên/context của chính mình
4BPP before-initBeanPostProcessor.postProcessBeforeInitializationHook trước init — modify hoặc return proxy
5@PostConstructannotation methodInit logic dùng dep đã inject
6afterPropertiesSetInitializingBean interface(Hiếm) tương đương @PostConstruct, kế thừa từ Spring 1.x
7init-method@Bean(initMethod = "...")Init khi bean third-party (không annotate được)
8BPP after-initBeanPostProcessor.postProcessAfterInitializationHook sau init — Spring AOP wrap proxy ở đây
9Destroy@PreDestroy, DisposableBean, destroy-methodCleanup khi container shutdown

3 mechanism (annotation / interface / method) cùng làm việc giống nhau (init hoặc destroy) tồn tại do lịch sử:

  • Spring 1-2: chỉ có InitializingBean interface và XML init-method.
  • Spring 2.5+: thêm @PostConstruct (chuẩn JSR-250).
  • Khuyến nghị 2026: dùng @PostConstruct/@PreDestroy — chuẩn, không lock vào Spring, đọc dễ.

2. Bước 1-2 — instantiate và populate

@Service
public class OrderService {
    private final PaymentGateway payment;       // constructor inject
    @Autowired private NotificationClient notif; // field inject

    public OrderService(PaymentGateway payment) {
        this.payment = payment;
        // Tai day: 'payment' SET, 'notif' van NULL
        System.out.println(notif);  // null !!
    }
}

Constructor chạy trước field injection. Vì sao? Spring phải gọi new mới có instance — chỉ sau đó mới có chỗ để set field. Đây là ràng buộc của Java, không phải design Spring.

Hệ quả thực tế: không dùng @Autowired field trong constructor. Đó là 1 trong nhiều lý do constructor injection được khuyến nghị: tất cả dependency đã có khi constructor chạy.

// SAI — payment se duoc inject, nhung @Autowired field thi chua
public OrderService(PaymentGateway payment) {
    this.payment = payment;
    notif.send("init");   // NPE
}

// DUNG — tat ca dep o constructor, khong field injection
public OrderService(PaymentGateway payment, NotificationClient notif) {
    this.payment = payment;
    this.notif = notif;
    notif.send("init");   // OK
}

3. Bước 3 — *Aware callbacks

Spring có ~10 interface *Aware mà bean có thể implement để được nhận tham chiếu của infrastructure:

InterfaceBean nhận được gìKhi nào hữu ích
BeanNameAwaresetBeanName(String) — tên của bean trong contextLog/debug khi bean cần biết identity
BeanFactoryAwaresetBeanFactory(BeanFactory) — factory đã tạo beanHiếm
ApplicationContextAwaresetApplicationContext(ApplicationContext) — contextKhi cần lookup động (legacy)
EnvironmentAwaresetEnvironment(Environment) — environment objectĐọc property tự custom
ResourceLoaderAwaresetResourceLoader(ResourceLoader)Load resource động
MessageSourceAwaresetMessageSource(MessageSource) — i18nCustom i18n logic
ApplicationEventPublisherAwaresetApplicationEventPublisher(...) — event busPublish custom event
BeanClassLoaderAwaresetBeanClassLoader(ClassLoader)Module với multiple classloader
EmbeddedValueResolverAwaresetEmbeddedValueResolver(StringValueResolver)Resolve ${...} trong string

Khuyến nghị không implement *Aware trong code business — đó là tight coupling vào Spring API. Thay vào đó:

// THAY VI:
@Service
public class BadService implements ApplicationContextAware {
    private ApplicationContext ctx;
    public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }
}

// LAM:
@Service
public class GoodService {
    private final ApplicationContext ctx;
    public GoodService(ApplicationContext ctx) { this.ctx = ctx; }
}

Constructor injection cho phép inject cả ApplicationContext, Environment, ApplicationEventPublisher — không cần *Aware. Spring tự deduce 7+ "infrastructure type" có thể inject như bean thường.

*Aware chỉ nên dùng khi viết infrastructure code (BFPP, custom scope, integration với thư viện ngoài cần callback đặc biệt). 99% application code không cần.

4. Bước 4-8 — init phase, BPP, AOP proxy

Đây là phase quan trọng nhất, nơi Spring wrap proxy cho các bean cần AOP.

4.1 BPP before-init (bước 4)

Mỗi BeanPostProcessor được register trong context có 2 method:

public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String name) { return bean; }
    default Object postProcessAfterInitialization(Object bean, String name) { return bean; }
}

Spring chạy postProcessBeforeInitialization của tất cả BPP lên bean, theo thứ tự @Order. BPP có thể trả về object khác (replace bean). Ít BPP làm điều này — phần lớn trả nguyên bean.

4.2 Init callbacks (bước 5-7)

Spring chạy init callback theo thứ tự cố định:

  1. @PostConstruct method (annotation chuẩn JSR-250).
  2. afterPropertiesSet() nếu bean implement InitializingBean.
  3. Custom init-method khai báo qua @Bean(initMethod = "init").

Nếu bean có cả 3, cả 3 đều chạy theo thứ tự trên. Hiếm khi cần > 1.

@Service
public class OrderService {
    private final DataSource ds;

    public OrderService(DataSource ds) { this.ds = ds; }

    @PostConstruct
    public void init() {
        // chay sau khi tat ca dep da set, BPP-before da chay
        try (var conn = ds.getConnection()) {
            conn.createStatement().execute("SELECT 1");
        }
        System.out.println("OrderService initialized");
    }
}

@PostConstruct lý tưởng cho:

  • Validate cấu hình tại startup (fail fast nếu config sai).
  • Pre-load cache.
  • Kiểm tra connection dependency.

Cảnh báo: exception ném từ @PostConstructapp fail to start. Đây là intended behavior — fail fast.

4.3 init-method cho bean third-party

Khi bean là class từ thư viện ngoài (không thể annotate @PostConstruct), dùng initMethod:

@Configuration
public class CacheConfig {

    @Bean(initMethod = "start", destroyMethod = "stop")
    public CacheCluster cacheCluster() {
        return new CacheCluster(/* config */);
    }
}

Spring gọi cacheCluster.start() sau init, cacheCluster.stop() khi shutdown.

Trick: nếu bean có method close() (vd Closeable/AutoCloseable), Spring tự gọi nó tại shutdown — không cần khai báo destroyMethod. Đây là (inferred) mode default từ Spring 4.

4.4 BPP after-init (bước 8) — nơi AOP magic

public interface BeanPostProcessor {
    default Object postProcessAfterInitialization(Object bean, String name) { return bean; }
}

AnnotationAwareAspectJAutoProxyCreator (BPP của AOP) hoạt động ở đây. Nó kiểm tra bean có match aspect nào không — nếu có, wrap bean trong proxy và return proxy thay vì bean gốc.

flowchart LR
    Bean["OrderService<br/>(plain)"]
    BPP["AOP BPP<br/>postProcessAfterInitialization"]
    Proxy["OrderService Proxy<br/>(CGLIB hoac JDK)"]

    Bean --> BPP --> Proxy
    Proxy -.->|"delegate"| Bean

    style Proxy fill:#fef3c7

Hệ quả: khi bạn @Autowired OrderService, biến tham chiếu đến PROXY, không phải bean gốc. Mọi method call qua proxy — proxy chèn logic AOP (transaction, security, log) trước/sau khi gọi method gốc.

Đây là lý do:

  • Method private/final không bị AOP chặn — proxy không override được.
  • Tự gọi method (this.method()) không qua proxy — proxy chỉ chặn call từ ngoài.

4.5 JDK Dynamic Proxy vs CGLIB Proxy — 2 cơ chế

Spring có 2 cách tạo proxy:

AspectJDK Dynamic ProxyCGLIB Proxy
Yêu cầuBean implement interfaceBean class không cần interface
Cơ chếProxy.newProxyInstance() — tạo proxy implement interfaceBytecode generation — tạo subclass extend bean class
Class proxy$Proxy42 (anonymous)OrderService$$EnhancerByCGLIB$$abc123
instanceof BeanClassfalsetrue
Method finalOK (interface không có final)Không proxy được — final không override được
Class finalOKKhông proxy được — final không subclass được
DependencyJava SE built-inCần cglib jar (đóng gói trong Spring)
// Truong hop 1: JDK proxy
public interface PaymentGateway { void charge(Money m); }

@Service
public class StripePayment implements PaymentGateway {
    @Transactional
    public void charge(Money m) { ... }
}

@Autowired
PaymentGateway payment;       // JDK proxy implement PaymentGateway

@Autowired
StripePayment payment;        // FAIL voi JDK proxy — proxy khong la subclass StripePayment
                              // Spring fall back ve CGLIB
// Truong hop 2: CGLIB proxy
@Service
public class OrderService {           // KHONG implement interface
    @Transactional
    public void place(Order o) { ... }
}

@Autowired
OrderService service;         // CGLIB proxy = subclass cua OrderService

Spring Boot 2.0+ ép CGLIB cho mọi @Configuration và mọi bean với @Transactional/@Async (spring.aop.proxy-target-class=true). Nhất quán hơn, không phụ thuộc bean có interface hay không.

Hệ quả thực tế:

  • Code business: không quan tâm đâu là JDK vs CGLIB — cứ inject qua interface.
  • Debug downcast: nếu code có (StripePayment) gateway, có thể fail trên JDK proxy mode. Best practice: code chỉ depend vào interface.
  • Native image (GraalVM): CGLIB cần dynamic class generation → khó với native. Spring Boot 3 có support nhưng cần config proxyBeanMethods = false cho @Configuration.

Pitfall classic — @Transactional không hoạt động với self-call:

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order o) {
        validate(o);   // tu goi -- KHONG qua proxy -- @Transactional duoi khong active
    }

    @Transactional
    public void validate(Order o) { /* ... */ }
}

Fix: split class hoặc inject self:

@Service
public class OrderService {
    @Autowired private OrderService self;   // inject proxy cua chinh minh

    @Transactional
    public void placeOrder(Order o) {
        self.validate(o);   // qua proxy -> @Transactional active
    }
}

(Hoặc tốt hơn: refactor — tách thành 2 class.)

5. Bước 9 — destroy callbacks

Khi container shutdown (ctx.close() hoặc JVM SIGTERM với registerShutdownHook), Spring chạy destroy callback theo thứ tự ngược chiều dependency:

  1. @PreDestroy method.
  2. destroy() nếu bean implement DisposableBean.
  3. Custom destroy-method khai báo qua @Bean(destroyMethod = "...").

Spring tự deduce destroy method cho các pattern phổ biến: nếu bean có method close() (vd Hikari connection pool), Spring tự gọi nó tại shutdown — không cần khai báo gì. Đây là feature @Bean(destroyMethod = "(inferred)") — default từ Spring 4.

@Service
public class CacheWarmer {
    private ScheduledExecutorService scheduler;

    @PostConstruct
    public void start() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(this::refreshCache, 0, 5, TimeUnit.MINUTES);
    }

    @PreDestroy
    public void stop() {
        if (scheduler != null) {
            scheduler.shutdown();
            try {
                scheduler.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Cảnh báo: destroy callback chỉ chạy nếu container shutdown đúng cách:

  • Spring Boot tự register shutdown hook → kill <pid> (SIGTERM) trigger graceful shutdown.
  • kill -9 <pid> (SIGKILL) không trigger — JVM bị giết trực tiếp, không kịp callback.
  • Crash JVM (OOM, segfault) cũng không trigger.

6. SmartLifecycle — kiểm soát thứ tự start/stop

Cho bean cần kiểm soát start/stop thứ tự cụ thể, Spring có SmartLifecycle:

@Component
public class KafkaConsumer implements SmartLifecycle {
    private volatile boolean running;

    public void start() {
        // start consumer thread
        running = true;
    }

    public void stop() {
        running = false;
        // close consumer
    }

    public boolean isRunning() { return running; }

    // Phase: bean voi phase nho start truoc, stop sau
    public int getPhase() { return 0; }

    // autoStartup = true → start() goi trong refresh()
    public boolean isAutoStartup() { return true; }

    // Async stop callback voi timeout
    public void stop(Runnable callback) { stop(); callback.run(); }
}

SmartLifecycle tốt hơn @PostConstruct/@PreDestroy cho:

  • Graceful shutdown sequence: Tomcat connector phase cao → stop trước (ngừng nhận request mới), Kafka consumer phase thấp → stop sau (xử lý request đang chạy).
  • Resume sau pause: gọi start() lại sau khi stop()@PostConstruct chỉ chạy 1 lần.

Spring Boot dùng SmartLifecycle cho web server, scheduling, Kafka, RabbitMQ — bạn ít khi tự implement, nhưng biết để debug khi log "Stopping bean X with phase 2147483646".

7. Lifecycle theo scope

9 giai đoạn trên áp dụng đầy đủ cho singleton scope. Scope khác có biến thể (sẽ đào sâu bài 05):

ScopeKhi nào instantiateKhi nào destroy
singletonContainer start (bước 11 của refresh)Container shutdown
prototypeMỗi lần getBean()Container không track destroy — caller chịu trách nhiệm
requestMỗi HTTP requestEnd of request
sessionMỗi HTTP sessionEnd of session

Pitfall: @PreDestroy trên prototype bean không chạy — Spring docs ghi rõ: "the lifecycle of a prototype bean is somewhat different. Spring does not manage the complete lifecycle".

8. Graceful shutdown production

Production yêu cầu graceful shutdown — nhận SIGTERM, ngừng accept request mới, xử lý nốt request in-flight, rồi shutdown. Spring Boot 2.3+ có sẵn:

# application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Quá trình:

sequenceDiagram
    participant K8s
    participant App
    participant Tomcat
    participant InFlight as In-flight requests
    participant Bean

    K8s->>App: SIGTERM
    App->>Tomcat: stop accepting new connections
    Tomcat-->>App: stop accepting (immediate)
    Note over Tomcat: 503 cho request moi
    Tomcat->>InFlight: wait for completion
    Note over InFlight: max 30s (timeout-per-shutdown-phase)
    InFlight-->>Tomcat: all done
    Tomcat-->>App: stopped
    App->>Bean: @PreDestroy callbacks
    App->>K8s: process exit 0

Pitfall production:

Vấn đềHệ quả
Không cấu hình server.shutdown=gracefulTomcat đóng socket ngay, in-flight request bị cắt → user 502
Không cấu hình K8s terminationGracePeriodSeconds đủ dài (default 30s)K8s SIGKILL khi quá hạn → callback không chạy
Bean @PreDestroy chạy lâu (> 30s)Timeout → state không được flush
Liveness probe trả UP trong khi shutdownK8s nghĩ pod còn lành mạnh, route traffic mới đến pod đang chết

Best practice K8s deployment:

spec:
  terminationGracePeriodSeconds: 60          # > spring.lifecycle.timeout
  containers:
    - name: app
      readinessProbe:                        # tat khi shutdown
        httpGet: { path: /actuator/health/readiness, port: 8080 }
      lifecycle:
        preStop:
          exec:
            command: ["sleep", "5"]          # cho service mesh propagate "removing endpoint"

9. Pitfall tổng hợp

Nhầm 1: Dùng @Autowired field trong constructor.

@Autowired private Foo foo;
public OrderService() {
    foo.doStuff();   // NPE — @Autowired chua chay
}

✅ Constructor injection — public OrderService(Foo foo) { ... }.

Nhầm 2: Exception trong @PostConstruct mà không xử lý.

@PostConstruct
public void init() {
    cache.preload();   // throws — app fail to start
}

✅ Đó là intended (fail fast). Nếu bạn muốn graceful: catch exception, log, continue. Đừng swallow lỗi config nghiêm trọng.

Nhầm 3: Self-call method @Transactional.

public void a() { this.b(); }
@Transactional public void b() { ... }   // tx khong active

✅ Inject self bean, hoặc tách class, hoặc dùng AspectJ load-time weaving (advanced).

Nhầm 4: Dùng InitializingBean cho bean business.

public class OrderService implements InitializingBean {
    public void afterPropertiesSet() { ... }
}

✅ Dùng @PostConstruct — chuẩn JSR-250, không lock Spring API.

Nhầm 5: Tin tưởng @PreDestroy chạy trên prototype. ✅ Spring không manage destroy của prototype. Caller cleanup, hoặc dùng BeanFactory.destroyBean(bean).

Nhầm 6: SIGKILL container production. ✅ Cấu hình graceful shutdown: server.shutdown=graceful, timeout đủ để in-flight request hoàn thành.

Nhầm 7: Implement *Aware để gọi getBean() — anti-pattern.

@Service
public class BadService implements ApplicationContextAware {
    private ApplicationContext ctx;
    public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }
    public void use() { ctx.getBean(Foo.class).doStuff(); }   // service locator
}

✅ Inject Foo qua constructor. *Aware chỉ cho infrastructure code.

Nhầm 8: Bean class final với @Transactional.

@Service
@Transactional
public final class OrderService { ... }   // CGLIB khong subclass duoc → startup fail

✅ Bỏ final (hoặc dùng JDK proxy với interface). Spring Boot 2.0+ default CGLIB nên final thường gây bug.

10. 📚 Deep Dive Spring Reference

📚 Tài liệu chính chủ

Reference docs:

JSR chuẩn:

Source để đọc khi cần đi sâu:

Ghi chú: doCreateBean là method ~80 dòng, bóc nó đọc 1 lần là hiểu sâu lifecycle hơn đọc 100 blog. Spring mã nguồn mở — tận dụng.

11. Tóm tắt

  • Bean trải qua 9 giai đoạn cố định: instantiate → populate → *Aware → BPP-before → @PostConstruct → afterPropertiesSet → init-method → BPP-after → ready, và destroy khi shutdown.
  • Constructor chạy trước @Autowired field — không dùng field inject trong constructor.
  • 3 cách init (@PostConstruct, InitializingBean, init-method) tồn tại do lịch sử. Khuyến nghị @PostConstruct (chuẩn JSR-250). init-method cho bean third-party.
  • 9 *Aware interface — chỉ dùng cho infrastructure code, không dùng trong business class. Constructor inject thay thế hết.
  • BPP có 2 hook: before-init và after-init. AOP proxy wrap ở after-init — đó là lý do @Transactional không hoạt động với self-call.
  • 2 cơ chế proxy: JDK (interface-based, anonymous Proxy class) và CGLIB (subclass bytecode). Boot 2.0+ ép CGLIB cho consistency.
  • SmartLifecycle kiểm soát thứ tự start/stop với phase — Spring Boot dùng cho web server, Kafka, scheduling.
  • Destroy callback chỉ chạy khi container shutdown đúng cách (SIGTERM với shutdown hook), không chạy với SIGKILL/crash.
  • Prototype bean không có destroy — Spring không track. Caller phải cleanup.
  • Graceful shutdown production: server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s + K8s terminationGracePeriodSeconds=60 + readiness probe.
  • Bean class final + @Transactional → CGLIB fail. Tránh final cho bean class.

12. Tự kiểm tra

Tự kiểm tra
Q1
Đoạn sau in gì? Vì sao?
@Service
public class S {
  @Autowired private Foo foo;

  public S() {
      System.out.println("ctor: " + foo);
  }
  @PostConstruct
  void init() {
      System.out.println("init: " + foo);
  }
}

In:

ctor: null
init: <Foo bean instance>

Vì sao: constructor chạy ở bước 1 của lifecycle — Java buộc gọi new trước, lúc này chưa có instance để set field. @Autowired field được populate ở bước 2 (populate properties). @PostConstruct chạy ở bước 5 — tất cả field đã set xong.

Bài học: không dùng @Autowired field trong constructor. Constructor injection (đưa Foo vào parameter của constructor) hoặc dùng @PostConstruct nếu logic phụ thuộc nhiều field.

Q2
Service sau có @Transactional trên cả 2 method. Khi gọi transferAndAudit() từ controller, transaction nào được áp dụng cho auditLog? Tx mới hay cùng tx với transferAndAudit?
@Service
public class AccountService {
  @Transactional
  public void transferAndAudit(...) {
      transfer(...);
      auditLog(...);
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void auditLog(...) { ... }
}

Cùng tx với transferAndAudit. Self-call (this.auditLog() qua method body) không qua proxy@Transactional annotation trên auditLog bị bypass hoàn toàn. REQUIRES_NEW không effect.

Lý do: AOP proxy wrap object từ ngoài. Khi controller gọi service.transferAndAudit(), call đi qua proxy → proxy chèn tx logic → gọi method gốc. Trong method gốc, this.auditLog() là direct method call trên object gốc — không có proxy ở giữa → annotation không trigger.

Cách fix:

  • Tách AuditService riêng, inject vào AccountService.
  • Inject self qua @Autowired private AccountService self rồi gọi self.auditLog(...) (workaround, không clean).
  • Dùng AspectJ load-time weaving (loại bỏ proxy, weaving tại bytecode level — advanced).
Q3
App Spring Boot deploy production. DevOps thắc mắc: khi pod bị Kubernetes terminate, vì sao log không thấy "OrderService destroyed" mặc dù bean có @PreDestroy? Liệt kê 4 nguyên nhân khả thi.
  • 1. K8s gửi SIGKILL thay SIGTERM: nếu container không respond SIGTERM trong terminationGracePeriodSeconds (default 30s), K8s gửi SIGKILL — JVM bị giết ngay, callback không chạy. Fix: tăng grace period, hoặc đảm bảo app không stuck.
  • 2. Graceful shutdown chưa enable: Spring Boot 2.3+ có server.shutdown=graceful nhưng tắt mặc định. Không enable → server đóng socket ngay khi nhận SIGTERM, in-flight request bị cắt, callback có chạy nhưng có thể fail nếu phụ thuộc resource đang đóng. Fix: server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s.
  • 3. Bean là prototype scope: Spring không quản lifecycle prototype hoàn toàn — @PreDestroy không được gọi tự động. Fix: dùng singleton hoặc tự cleanup trong code business.
  • 4. Log không flush trước khi process exit: SLF4J/Logback async appender có thể chưa flush. Log có in nhưng appender chưa write trước SIGKILL. Fix: dùng sync appender cho shutdown log, hoặc flush explicit trước exit.

Cách verify nhanh: log System.out.println("SIGTERM received") trong shutdown hook tự đăng ký, xem có in không. Nếu không in → SIGKILL/crash. Nếu in → vấn đề ở callback hoặc scope.

Q4
Spring Framework có cả InitializingBean interface và @PostConstruct annotation cho cùng mục đích. Vì sao cả 2 cùng tồn tại? Khi nào nên dùng cái nào?

Cùng tồn tại do lịch sử:

  • InitializingBean có từ Spring 1.0 (2004) — lúc đó Java chưa có annotation chuẩn cho lifecycle.
  • @PostConstruct chuẩn JSR-250 ra 2006, Spring 2.5 (2007) thêm support. Dùng được ngoài Spring (CDI, EJB 3, Java EE).

Spring giữ InitializingBean để backward compat — code legacy không bị broken.

Khi nào dùng cái nào:

  • @PostConstruct: 99% trường hợp. Chuẩn, không lock Spring, IDE highlight tốt.
  • InitializingBean: chỉ khi bạn viết framework/library cần init logic mà không muốn require classpath JSR-250 (rất hiếm — JSR-250 là jakarta.annotation nhỏ, gần như luôn có sẵn).
  • @Bean(initMethod = "..."): khi bean là class third-party, không sửa source được (vd HikariDataSource.start()). Annotate trên method @Bean trong config.
Q5
Bean @Service@Transactional trên 1 method. Bạn @Autowired bean này vào controller. controllerService instanceof RealServiceClass trả về true hay false? Vì sao?

Câu trả lời: tuỳ thuộc proxy mode.

  • CGLIB proxy (default Spring Boot từ 2.0): true. CGLIB tạo subclass của RealServiceClass tại runtime. Object inject là subclass — vẫn instanceof class gốc.
  • JDK dynamic proxy: false. JDK proxy chỉ implement interface của bean — controllerService là instance của proxy class, không extend RealServiceClass.

Spring chọn proxy mode theo:

  • Bean implement interface → JDK dynamic proxy (default Spring Framework).
  • Bean không implement interface → CGLIB.
  • Spring Boot 2.0+ ép CGLIB cho mọi bean (spring.aop.proxy-target-class=true) — nhất quán hơn.

Hệ quả thực tế: nếu bạn downcast bean về class gốc, code có thể fail trên JDK proxy mode. Best practice: code chỉ depend vào interface, không downcast.

Cách verify: print controllerService.getClass().getName() — nếu chứa $$EnhancerByCGLIB$$ hoặc $$SpringCGLIB$$ → CGLIB; chứa $Proxy → JDK.

Q6
Bạn cần graceful shutdown cho web app deploy K8s. Liệt kê 4 cấu hình cần phối hợp giữa Spring Boot và K8s manifest, và giải thích vai trò mỗi cái.

4 cấu hình:

  1. server.shutdown=graceful (Spring Boot): khi nhận SIGTERM, Tomcat ngừng accept connection mới (trả 503), nhưng cho phép in-flight request chạy tiếp đến hoàn thành. Không có config này → socket đóng ngay → user 502.
  2. spring.lifecycle.timeout-per-shutdown-phase=30s (Spring Boot): timeout cho mỗi phase shutdown. Nếu in-flight request không xong trong 30s, force terminate. Cân bằng giữa "đợi user" vs "shutdown nhanh".
  3. terminationGracePeriodSeconds=60 (K8s pod spec): K8s đợi tối đa 60s sau khi gửi SIGTERM trước khi gửi SIGKILL. Phải lớn hơn Spring timeout để Spring có thời gian shutdown gracefully. Rule: K8s grace period = Spring timeout + buffer 20-30s cho pre-stop hook + flush log.
  4. Readiness probe + pre-stop hook: probe trỏ /actuator/health/readiness. Khi shutdown, Spring tự đánh dấu readiness DOWN → K8s ngừng route traffic mới đến pod. Pre-stop hook sleep 5 cho service mesh propagate "removing endpoint" trước khi pod thực sự stop. Không có 2 cái này → traffic mới vẫn đến pod đang chết → 502.

Workflow đúng:

SIGTERM → Spring marks readiness DOWN → K8s stop routing new traffic
      → preStop sleep 5s → service mesh propagate
      → Tomcat stop accept new connections → in-flight finish (max 30s)
      → @PreDestroy → process exit
      → K8s deletes pod (within 60s total)
Q7
Bạn viết `WeatherService` cần auto refresh data mỗi giờ. Implement bằng SmartLifecycle hay @PostConstruct + ScheduledExecutor? Khi nào nên chọn cái nào?

Default: @PostConstruct + @Scheduled (Spring Scheduling support).

@Service
public class WeatherService {
  @Scheduled(fixedRate = 3600000)   // 1 hour
  public void refresh() { ... }
}

Đơn giản, Spring tự manage scheduler thread pool, tự stop khi shutdown. Đủ cho 95% case.

Chọn SmartLifecycle khi:

  • Cần kiểm soát thứ tự start/stop: vd start sau bean DataSource ready, stop trước bean CacheManager.
  • Cần resume sau pause: management endpoint cho phép pause/resume manually qua HTTP.
  • Cần graceful drain: khi stop, đợi tất cả in-flight refresh xong trước khi return — implement qua stop(Runnable callback).

Ví dụ SmartLifecycle hợp lý: Kafka consumer phải chờ EntityManagerFactory ready trước khi consume (DB available), và phải drain in-flight message khi shutdown trước khi EntityManagerFactory close. Phase ordering giải quyết.

Quy tắc: bắt đầu với @PostConstruct/@Scheduled, refactor sang SmartLifecycle nếu gặp ordering problem cụ thể. Không over-engineer.

Bài tiếp theo: Bean scopes — singleton, prototype, request, session — chọn cái nào

Bài này có giúp bạn hiểu bản chất không?

Bình luận (0)

Đang tải...