99 Ngày Spring — Ngày 06: @Configuration & @Bean
Ngày 05 khép lại bằng một câu hỏi treo: máy quét chỉ thấy class của bạn — còn class của thư viện ngoài, nơi không thể mở source ra dán @Component, thì sao? Hôm nay là câu trả lời: @Configuration và @Bean — đăng ký bean "thủ công" bằng Java thuần, và cũng là mảnh ghép cuối để mai này đọc hiểu auto-configuration của Spring Boot.

@Bean — công thức nấu bean bằng tay
@Configuration đánh dấu một class là "sổ công thức"; bên trong, mỗi method mang @Bean là một công thức: Spring gọi method và đưa giá trị trả về vào container như mọi bean khác. Còn chính config class thì ai tìm ra? Vẫn là component scan của Ngày 05 — @Configuration cũng là một @Component chuyên môn hóa, nên nó phải nằm dưới package gốc như thường lệ:
@Configuration // class "sổ công thức" — bản thân nó cũng là beanpublic class AppConfig {
@Bean // tên bean mặc định = tên method: "objectMapper"
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return mapper; // giá trị trả về được đưa vào container
}
}Tên bean mặc định = tên method (
objectMapper) — muốn khác thì@Bean("tenKhac").Với container, bean từ
@Beanvà bean từ@Componentbình đẳng tuyệt đối — tiêm vào nơi khác bằng constructor y như Ngày 04.Method
@Beanchạy một lần lúc khởi động (scope singleton mặc định) — không phải mỗi lần ai đó cần bean.
Khi nào cần @Bean?
Class của thư viện ngoài —
ObjectMappercủa Jackson,RestClient,DataSource… bạn không sửa được source của họ.Khởi tạo cần logic — builder nhiều bước, đọc cấu hình, chọn implementation theo điều kiện.
Nhiều bean cùng kiểu với cấu hình khác nhau — ví dụ 2
RestClienttrỏ 2 API.
@Configurationpublic class HttpConfig {
@Bean
public RestClient githubClient() {
return RestClient.builder() // class của thư viện —
.baseUrl("https://api.github.com") // không thể mở source ra
.build(); // dán @Component được!
}
}Quy tắc nhớ nhanh: class của mình → dán nhãn (@Component/@Service… — Ngày 05); class của người khác hoặc cần logic khởi tạo → viết công thức @Bean.
Tiêm phụ thuộc giữa các @Bean
@Configurationpublic class ReportConfig {
@Bean
public ReportFormatter formatter() {
return new ReportFormatter();
}
@Bean // tham số được container TIÊM vào — như constructor injection
public ReportService reportService(ReportFormatter formatter) {
return new ReportService(formatter);
}
@Bean // gọi thẳng formatter() cũng được — CGLIB chặn lời gọi
public ReportPrinter reportPrinter() {
return new ReportPrinter(formatter()); // → trả ĐÚNG bean singleton,
} // không tạo mới!
}Hai cách nối bean trong config: khai báo tham số cho method — container tự tiêm, đúng tinh thần constructor injection — hoặc gọi thẳng method @Bean khác. Cách thứ hai trông như "new mỗi lần gọi" nhưng không: Spring dựng một subclass CGLIB của config class, chặn mọi lời gọi method @Bean và trả về đúng bean singleton đang nằm trong container.
@Configuration(proxyBeanMethods = false)— "lite mode" — tắt phép thuật đó: gọi chéo method lúc này là new thật; Boot dùng nhiều trong auto-configuration để khởi động nhanh hơn.Vì vậy hãy ưu tiên kiểu tham số: rõ ràng, không phụ thuộc proxy, chuyển sang lite mode không vỡ.
@Component vs @Bean — chọn cái nào?
@Componentvà họ hàng: dán lên class của bạn, máy quét tự tìm — mặc định cho code của chính mình.@Beantrong@Configuration: công thức cho class ngoài, khởi tạo phức tạp, hoặc nhiều biến thể cùng kiểu.Trong container hai loại không khác gì nhau: cùng vòng đời, cùng được tiêm, cùng là singleton mặc định.
Và một bí mật nho nhỏ: auto-configuration của Spring Boot (Ngày 02) thực chất là hàng trăm class @Configuration viết sẵn với các @Bean có điều kiện — bạn thêm starter nào, bộ công thức ấy kích hoạt. Ngày 10 ta sẽ mở hộp đen này bằng @Conditional.
Bài tập nhỏ
Tạo
AppConfigvới@Beantrả vềObjectMappertùy chỉnh, tiêm vào một@Servicevà serialize thử một object cóLocalDate.Khai báo 2 bean RestClient cùng kiểu (2
baseUrlkhác nhau) rồi tiêm theo kiểu — đọc kỹ lỗi Spring báo; thử sửa bằng tên bean trùng tên tham số.In
getBeanDefinitionNames()(Ngày 03) và tìm các bean sinh từ config — xác nhận tên mặc định đúng bằng tên method.Đổi
@Configurationthành@Componentrồi gọi chéo 2 method@Bean— đoán xem còn là singleton không? (Gợi ý:@Componentchạy lite mode — không có CGLIB.)
Kết luận
Giờ bạn có đủ hai con đường tạo bean: dán nhãn cho class của mình, viết công thức @Bean cho class của người khác — và hiểu vì sao gọi chéo method trong @Configuration vẫn ra singleton. Nhưng singleton là gì mà nhắc mãi? Ngày 07: bean scope & lifecycle — singleton/prototype/request, @PostConstruct & @PreDestroy. 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.


