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:#feeBảng tóm tắt từng bước:
| # | Giai đoạn | Code/annotation | Khi nào dùng |
|---|---|---|---|
| 1 | Instantiate | constructor | Bean class được new với DI qua constructor |
| 2 | Populate | @Autowired field/setter | Field/setter inject cho dep không qua constructor |
| 3 | *Aware | implement BeanNameAware, ApplicationContextAware, ... | Bean cần biết tên/context của chính mình |
| 4 | BPP before-init | BeanPostProcessor.postProcessBeforeInitialization | Hook trước init — modify hoặc return proxy |
| 5 | @PostConstruct | annotation method | Init logic dùng dep đã inject |
| 6 | afterPropertiesSet | InitializingBean interface | (Hiếm) tương đương @PostConstruct, kế thừa từ Spring 1.x |
| 7 | init-method | @Bean(initMethod = "...") | Init khi bean third-party (không annotate được) |
| 8 | BPP after-init | BeanPostProcessor.postProcessAfterInitialization | Hook sau init — Spring AOP wrap proxy ở đây |
| 9 | Destroy | @PreDestroy, DisposableBean, destroy-method | Cleanup 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ó
InitializingBeaninterface và XMLinit-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:
| Interface | Bean nhận được gì | Khi nào hữu ích |
|---|---|---|
BeanNameAware | setBeanName(String) — tên của bean trong context | Log/debug khi bean cần biết identity |
BeanFactoryAware | setBeanFactory(BeanFactory) — factory đã tạo bean | Hiếm |
ApplicationContextAware | setApplicationContext(ApplicationContext) — context | Khi cần lookup động (legacy) |
EnvironmentAware | setEnvironment(Environment) — environment object | Đọc property tự custom |
ResourceLoaderAware | setResourceLoader(ResourceLoader) | Load resource động |
MessageSourceAware | setMessageSource(MessageSource) — i18n | Custom i18n logic |
ApplicationEventPublisherAware | setApplicationEventPublisher(...) — event bus | Publish custom event |
BeanClassLoaderAware | setBeanClassLoader(ClassLoader) | Module với multiple classloader |
EmbeddedValueResolverAware | setEmbeddedValueResolver(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:
@PostConstructmethod (annotation chuẩn JSR-250).afterPropertiesSet()nếu bean implementInitializingBean.- 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ừ @PostConstruct → app 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:#fef3c7Hệ 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/finalkhô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:
| Aspect | JDK Dynamic Proxy | CGLIB Proxy |
|---|---|---|
| Yêu cầu | Bean implement interface | Bean class không cần interface |
| Cơ chế | Proxy.newProxyInstance() — tạo proxy implement interface | Bytecode generation — tạo subclass extend bean class |
| Class proxy | $Proxy42 (anonymous) | OrderService$$EnhancerByCGLIB$$abc123 |
instanceof BeanClass | false | true |
Method final | OK (interface không có final) | Không proxy được — final không override được |
Class final | OK | Không proxy được — final không subclass được |
| Dependency | Java SE built-in | Cầ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 = falsecho@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:
@PreDestroymethod.destroy()nếu bean implementDisposableBean.- 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 khistop()—@PostConstructchỉ 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):
| Scope | Khi nào instantiate | Khi nào destroy |
|---|---|---|
singleton | Container start (bước 11 của refresh) | Container shutdown |
prototype | Mỗi lần getBean() | Container không track destroy — caller chịu trách nhiệm |
request | Mỗi HTTP request | End of request |
session | Mỗi HTTP session | End 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 0Pitfall production:
| Vấn đề | Hệ quả |
|---|---|
Không cấu hình server.shutdown=graceful | Tomcat đó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 shutdown | K8s 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
Reference docs:
- Spring Framework Reference — Customizing the Nature of a Bean — section đầy đủ về lifecycle callback,
*Aware, init/destroy method. - Spring Framework Reference — @PostConstruct and @PreDestroy — annotation chuẩn cho lifecycle.
- Spring Framework Reference — BeanPostProcessor — interface mở rộng quan trọng nhất.
- Spring Framework Reference — Lifecycle Callbacks — combined effect, SmartLifecycle, phase.
- Spring Framework Reference — Proxying Mechanisms (AOP) — JDK vs CGLIB, self-call problem.
- Spring Boot Reference — Graceful Shutdown — config + behavior.
JSR chuẩn:
- JSR-250 — Common Annotations for the Java Platform — định nghĩa
@PostConstruct,@PreDestroy. Spring tuân thủ chuẩn này từ 2.5.
Source để đọc khi cần đi sâu:
AbstractAutowireCapableBeanFactory.doCreateBean()— method thực hiện toàn bộ 9 giai đoạn cho 1 bean. SearchpopulateBean,initializeBean.org.springframework.aop.framework.DefaultAopProxyFactory— chọn JDK vs CGLIB.
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
@Autowiredfield — 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-methodcho 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
@Transactionalkhô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.
SmartLifecyclekiể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+ K8sterminationGracePeriodSeconds=60+ readiness probe. - Bean class
final+@Transactional→ CGLIB fail. Tránhfinalcho bean class.
12. 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);
}
}
▸
@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.
Q2Service 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(...) { ... }
}
▸
@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
AuditServiceriêng, inject vàoAccountService. - Inject self qua
@Autowired private AccountService selfrồi gọiself.auditLog(...)(workaround, không clean). - Dùng AspectJ load-time weaving (loại bỏ proxy, weaving tại bytecode level — advanced).
Q3App 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.▸
@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=gracefulnhư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 —
@PreDestroykhô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.
Q4Spring 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?▸
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ử:
InitializingBeancó từ Spring 1.0 (2004) — lúc đó Java chưa có annotation chuẩn cho lifecycle.@PostConstructchuẩ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.annotationnhỏ, gần như luôn có sẵn).@Bean(initMethod = "..."): khi bean là class third-party, không sửa source được (vdHikariDataSource.start()). Annotate trên method@Beantrong config.
Q5Bean @Service có @Transactional trên 1 method. Bạn @Autowired bean này vào controller. controllerService instanceof RealServiceClass trả về true hay false? Vì sao?▸
@Service có @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ủaRealServiceClasstại runtime. Object inject là subclass — vẫninstanceofclass gốc. - JDK dynamic proxy:
false. JDK proxy chỉ implement interface của bean —controllerServicelà instance của proxy class, không extendRealServiceClass.
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.
Q6Bạ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:
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.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".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.- 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 hooksleep 5cho 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)Q7Bạ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?▸
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
DataSourceready, stop trước beanCacheManager. - 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...