99 Ngày Spring — Ngày 07: Bean scope & lifecycle
Sáu ngày qua ta nhắc đi nhắc lại chữ singleton như một điều hiển nhiên — hôm nay trả nợ: scope quyết định container tạo bao nhiêu instance cho một bean và chúng sống bao lâu. Kèm theo là vòng đời của bean — móc @PostConstruct/@PreDestroy để chen việc của mình vào lúc sinh và lúc mất — và @Lazy cho những bean nặng nề chưa cần vội.

Scope — một bean, bao nhiêu instance?
@Component // mặc định: SINGLETON — cả app dùng chung 1 instancepublic class CounterService { }
@Component@Scope("prototype") // PROTOTYPE — MỖI lần xin container là một instance MỚIpublic class ShoppingCart { }
// Web scope (cần ứng dụng web):// @RequestScope — mỗi HTTP request một instance// @SessionScope — mỗi phiên người dùng một instancesingleton (mặc định): container tạo đúng một instance lúc khởi động, ai xin cũng nhận nó — tiết kiệm và đủ dùng cho service/repository không giữ state.
prototype: mỗi lời gọi
getBean()/ mỗi điểm tiêm là một instance mới — cho object có state riêng theo người dùng.request / session (ứng dụng web): instance sống theo một HTTP request / một phiên — sẽ gặp lại ở giai đoạn REST API.
Con số nói thay: một app thực tế có hàng trăm bean singleton, prototype đếm trên đầu ngón tay. Mặc định cứ để singleton — chỉ đổi khi có lý do rõ ràng.
Cái bẫy lớn nhất: state trong singleton
@Service // singleton — MỘT instance phục vụ MỌI request đồng thờipublic class OrderService {
private int soDonHang = 0; // ⚠️ state ghi được trong singleton!
public void datHang() {
soDonHang++; // nhiều request cùng ghi → race condition
}
}
// Quy tắc vàng: bean singleton nên STATELESS —// field chỉ giữ dependency (final), dữ liệu đi theo tham số & biến cục bộMột instance phục vụ mọi request cùng lúc nghĩa là field ghi được trở thành tài nguyên tranh chấp: hai request cùng soDonHang++ có thể mất một lượt đếm. Quy tắc vàng: bean singleton stateless — field chỉ giữ dependency final (đúng kiểu constructor injection Ngày 04), dữ liệu chảy qua tham số và biến cục bộ. Đây cũng là câu phỏng vấn kinh điển: "Singleton bean có thread-safe không?" — không, Spring chỉ bảo đảm một instance, thread-safety là việc của bạn.
Còn một bẫy tinh vi: tiêm prototype vào singleton — prototype chỉ được tạo một lần lúc tiêm, rồi "đóng băng" trong singleton, thành ra cũng như singleton. Gặp ca này cần ObjectProvider để xin instance mới mỗi lần dùng — biết tên giải pháp là đủ cho hôm nay.
Vòng đời — chen việc vào lúc sinh, lúc mất
Hành trình một bean: container tạo instance → tiêm dependency → @PostConstruct → phục vụ → (container đóng) → @PreDestroy → kết thúc. Hai móc đó là chỗ đặt code khởi tạo và dọn dẹp:
@Componentpublic class CacheManager {
private final Map<String, String> cache = new HashMap<>();
@PostConstruct // chạy SAU khi bean tạo xong & tiêm đủ dependency
public void warmUp() {
cache.put("config", taiTuDatabase()); // nạp sẵn dữ liệu
}
@PreDestroy // chạy TRƯỚC khi container đóng — dọn dẹp
public void flush() {
ghiXuongDisk(cache);
}
}Vì sao không làm trong constructor? Lúc constructor chạy, dependency có thể chưa tiêm đủ —
@PostConstructbảo đảm bean đã "đủ đồ nghề".@PreDestroychạy khi container đóng êm đềm (Ctrl+C,close()) — kill cứng thì không kịp chạy.Lưu ý: bean prototype container tạo xong là "buông tay" —
@PreDestroykhông được gọi cho chúng.
@Lazy — khoan hãy tạo, chờ dùng mới tạo
Singleton mặc định là eager: tất cả được tạo lúc khởi động — lỗi cấu hình lộ ngay từ đầu, đổi lại app nặng khởi động chậm. Bean nào nặng và hiếm dùng thì đánh dấu @Lazy:
@Component@Lazy // đừng tạo lúc khởi động — chờ tới lần dùng ĐẦU TIÊNpublic class HeavyReportEngine {
public HeavyReportEngine() {
// nạp model 2GB — chậm 10 giây
}
}
@Servicepublic class ReportService {
private final HeavyReportEngine engine;
// Nơi tiêm cũng phải @Lazy — không thì engine vẫn bị tạo sớm
public ReportService(@Lazy HeavyReportEngine engine) {
this.engine = engine; // nhận proxy; engine thật tạo khi gọi method
}
}Đánh đổi công bằng: khởi động nhanh hơn, nhưng lỗi khởi tạo lộ muộn — thay vì chết ngay lúc boot, app chết ở lần dùng đầu tiên. Vì thế đừng rải @Lazy khắp nơi; eager-by-default chính là một tính năng an toàn của Spring.
Bài tập nhỏ
Tạo bean
@Scope("prototype"), gọigetBean()2 lần, so sánh==— rồi làm lại với singleton.Thêm
@PostConstructvà@PreDestroyin log vào một bean, chạy app rồi tắt bằng Ctrl+C — quan sát thứ tự log lúc khởi động và lúc đóng.Tái hiện bẫy đếm: field
inttrong singleton, gọi++từ 2 thread chạy song song 10.000 lần — kết quả có đủ 20.000 không?Đánh dấu một bean
@Lazyvới constructor in log — xác nhận log chỉ hiện ở lần dùng đầu tiên, không phải lúc khởi động.
Kết luận
Giờ chữ "singleton" đã có nội dung thật: scope quyết định số instance, vòng đời có hai móc @PostConstruct/@PreDestroy, singleton phải stateless, và @Lazy dành cho bean nặng hiếm dùng. Ngày 08 ta rời thế giới bean để cấu hình ứng dụng: application.yml, @Value và @ConfigurationProperties — đưa những con số hard-code ra khỏi code. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


