99 Ngày Spring — Ngày 04: các kiểu Dependency Injection
Hôm qua bạn đã thấy container tạo và nuôi bean. Nhưng còn mảnh ghép cuối: bean này cần bean kia thì container đưa vào bằng cách nào? Đó là dependency injection — và Spring cho bạn 3 cửa: constructor, setter và field. Hôm nay ta xem từng cửa, và lý do cả cộng đồng (lẫn chính Spring team) khuyên đi cửa số một.

Bài toán: bean cần bean
@Servicepublic class OrderService {
// OrderService cần PaymentService để làm việc.
// Làm sao container ĐƯA PaymentService vào đây?
}Container biết cả OrderService lẫn PaymentService (nhờ @Service — Ngày 03). Việc còn lại là chỉ chỗ cho container đặt PaymentService vào: qua tham số constructor, qua method setter, hay ghi thẳng vào field.
Kiểu 1: Constructor injection — khuyên dùng
@Servicepublic class OrderService {
private final PaymentService paymentService;
// 1 constructor duy nhất → không cần @Autowired
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}Field khai báo
finalđược — object bất biến sau khi tạo, không ai thay được phụ thuộc giữa chừng.Bean không bao giờ tồn tại ở trạng thái thiếu đồ nghề: thiếu phụ thuộc là fail ngay lúc khởi động, không phải NPE lúc nửa đêm.
Từ Spring 4.3: class chỉ có 1 constructor thì không cần
@Autowired— code sạch như POJO thuần.
Kiểu 2: Setter injection — cho phụ thuộc tùy chọn
@Servicepublic class NotificationService {
private SmsSender smsSender; // phụ thuộc TÙY CHỌN
@Autowired(required = false)
public void setSmsSender(SmsSender smsSender) {
this.smsSender = smsSender;
}
}Setter injection cho phép tạo bean trước, tiêm sau — hợp với phụ thuộc không bắt buộc (không có SmsSender thì thôi, chỉ gửi email) hoặc phụ thuộc muốn thay đổi lại được. Nhược điểm là object khả biến: ai đó có thể gọi setter lần nữa giữa vòng đời, và bạn phải tự kiểm tra null trước khi dùng.
Kiểu 3: Field injection — gọn mà đắt
@Servicepublic class ReportService {
@Autowired
private UserRepository userRepository; // tiêm thẳng vào field
// Nhìn gọn... nhưng là kiểu bị khuyên TRÁNH nhiều nhất!
}Nhìn ngắn nhất — không constructor, không setter — nên từng là kiểu phổ biến nhất trong tutorial cũ. Nhưng cái giá không nhỏ:
Khó test — muốn đặt phụ thuộc giả phải dùng reflection hoặc kéo cả Spring context vào unit test.
Che giấu phụ thuộc — class 15 field
@Autowirednhìn vẫn "gọn", trong khi constructor 15 tham số sẽ hét lên: class này ôm việc quá nhiều!Không dùng được
final— phụ thuộc có thể bị đổi bằng reflection bất cứ lúc nào.Tách khỏi Spring là chết: không container thì không có cách chính thống nào đặt phụ thuộc vào.
Vì sao chọn constructor ?
// Constructor injection → test không cần Spring:PaymentService fake = new FakePaymentService();
OrderService service = new OrderService(fake); // xong!
// Field injection → new xong field vẫn null,// phải nhờ reflection hoặc bật cả Spring context để test.Gom lại một câu: constructor injection cho bạn bất biến (final), an toàn (fail sớm), trung thực (lộ rõ mọi phụ thuộc) và dễ test (chỉ cần new). Tài liệu chính thức của Spring khuyến nghị: constructor cho phụ thuộc bắt buộc, setter cho phụ thuộc tùy chọn — còn field injection nên để dành cho... code demo. Mẹo thực dụng: constructor bắt đầu đông tham số chính là "chuông báo" class đang phình — tách nhỏ nó thay vì đổi sang field injection để giấu bụng.
Bài tập nhỏ
Tạo
PaymentServicevàOrderServicevới constructor injection, in một dòng log trong constructor để thấy thứ tự container tạo bean.Thử xóa
@ServicekhỏiPaymentServicevà chạy lại — đọc kỹ lỗirequired a bean of type: đây là fail-fast đang cứu bạn.Viết unit test cho
OrderServicebằng cáchnewnó với mộtPaymentServicegiả — không cần Spring.Đổi tạm sang field injection và thử viết lại test đó — cảm nhận sự khác biệt rồi... đổi về.
Kết luận
Bạn đã biết 3 con đường đưa phụ thuộc vào bean và lý do constructor injection là mặc định đúng đắn: final, fail sớm, trung thực, dễ test. Ngày 05, ta trả lời câu hỏi ngược lại: làm sao container tìm thấy các bean ngay từ đầu — @Component và component scan, cùng lỗi kinh điển "bean ngoài package không được quét". 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.


