@Bean & @Configuration: Cơ Chế CGLIB Full Và Lite Mode
Tìm hiểu cơ chế CGLIB proxy của @Configuration, so sánh Full mode và Lite mode, kỹ thuật khai báo bean third-party và tổ chức cấu hình với @Import.
TL;DR: @Bean là phương thức khai báo bean cho các lớp bên thứ ba không thể gắn @Component. Lớp mang annotation @Configuration được Spring nâng cấp bằng CGLIB subclass (chế độ Full Mode) để chặn mọi lời gọi phương thức @Bean, đảm bảo khi các hàm gọi lẫn nhau sẽ luôn trả về cùng một instance singleton đã lưu trong container. Tắt CGLIB bằng proxyBeanMethods = false (chế độ Lite Mode) giúp tăng tốc khởi động ứng dụng nhưng đòi hỏi kỷ luật: mọi quan hệ phụ thuộc phải tiêm qua tham số phương thức, tuyệt đối không gọi trực tiếp phương thức @Bean trong thân hàm.
Hiểu rõ hai chế độ này giúp bạn kiểm soát hoàn hảo việc chia sẻ tài nguyên (như Connection Pool) và tối ưu hóa thời gian khởi động của hệ thống.
1. Tại sao cần phương thức @Bean cho các thư viện bên thứ ba?
Annotation @Component yêu cầu bạn phải gắn trực tiếp lên định nghĩa lớp. Tuy nhiên, với các đối tượng từ thư viện ngoài như HikariDataSource hay ObjectMapper, bạn không thể chỉnh sửa mã nguồn của thư viện để thêm annotation. Phương thức @Bean giải quyết bài toán này bằng cách đóng vai trò một hàm nhà máy (factory method):
@Configuration
public class InfrastructureConfig {
@Bean
public DataSource dataSource(
@Value("${db.url}") String url,
@Value("${db.user}") String user,
@Value("${db.pass}") String pass) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(url);
ds.setUsername(user);
ds.setPassword(pass);
ds.setMaximumPoolSize(20);
return ds;
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
Tên của bean mặc định là tên phương thức, kiểu bean là kiểu trả về, và mọi tham số của hàm đều được Spring tự động giải quyết thông qua cơ chế tiêm phụ thuộc.
2. Vấn đề gì phát sinh khi các phương thức @Bean gọi trực tiếp lẫn nhau?
Giả sử bạn cần khởi tạo hai bean: một ConnectionPool và một TransactionManager phụ thuộc vào chính ConnectionPool đó:
// Truong hop khong co CGLIB proxy can thiep
public class DatabaseConfig {
@Bean
public ConnectionPool connectionPool() {
return new HikariConnectionPool();
}
@Bean
public TransactionManager transactionManager() {
// Loi goi Java thuan: Tao mot pool hoan toan moi!
return new DataSourceTransactionManager(connectionPool());
}
}
Nếu không có cơ chế đặc biệt can thiệp, lời gọi connectionPool() bên trong hàm transactionManager() chỉ là một lời gọi phương thức Java thuần túy. Hậu quả là new HikariConnectionPool() sẽ được thực thi thêm một lần nữa. Ứng dụng sẽ có hai pool kết nối độc lập tồn tại song song, gây lãng phí bộ nhớ nghiêm trọng và làm hỏng tính toàn vẹn của transaction.
3. CGLIB Proxy trong @Configuration Full Mode can thiệp như thế nào?
Để bảo toàn tính duy nhất của Singleton bean, khi phát hiện annotation @Configuration, Spring sử dụng thư viện CGLIB để sinh ra một lớp con kế thừa lớp cấu hình tại runtime (dạng DatabaseConfig$$EnhancerBySpringCGLIB):
@Configuration // Kich hoat Full Mode voi CGLIB proxy mac dinh
public class DatabaseConfig {
@Bean
public ConnectionPool connectionPool() {
return new HikariConnectionPool();
}
@Bean
public TransactionManager transactionManager() {
// Bi CGLIB intercept: Tra ve bean connectionPool da duoc cache
return new DataSourceTransactionManager(connectionPool());
}
}

CGLIB ghi đè (override) phương thức connectionPool(). Khi phương thức này được gọi, Proxy kiểm tra trước trong ApplicationContext. Nếu bean đã tồn tại, nó trả về instance có sẵn trong cache thay vì thực thi lại thân hàm.
Vì cơ chế dựa trên kế thừa lớp và ghi đè phương thức:
- Lớp mang
@Configurationkhông được có từ khóa final. - Các phương thức mang
@Beankhông được là private hoặc final.
4. Chế độ Lite Mode proxyBeanMethods = false hoạt động ra sao?
Từ Spring 5.2, bạn có thể tắt cơ chế sinh CGLIB proxy thông qua thuộc tính proxyBeanMethods = false:
@Configuration(proxyBeanMethods = false) // Chuyen sang Lite Mode
public class WebClientConfig {
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
@Bean
public UserApiClient userApiClient(WebClient.Builder builder) {
// Tiem thong qua tham so thay vi goi ham truc tiep
return new UserApiClient(builder.build());
}
}
Trong chế độ Lite Mode, Spring không tốn chi phí sinh bytecode subclass lúc startup, giúp ứng dụng khởi động nhanh hơn và cực kỳ thân thiện khi đóng gói sang GraalVM Native Image.
| Tiêu chí | Full Mode (proxyBeanMethods = true) | Lite Mode (proxyBeanMethods = false) |
|---|---|---|
| CGLIB Subclass | Luôn được sinh ra tại runtime | Không sinh proxy, dùng trực tiếp class gốc |
| Method @Bean gọi nhau | Được intercept, trả về singleton cache | Là lời gọi Java thuần, tạo mới instance |
| Tốc độ khởi động | Chậm hơn do chi phí sinh proxy | Nhanh hơn, ít tốn RAM |
| Tương thích Native Image | Phức tạp hơn do phản chiếu động | Tương thích hoàn hảo |
5. Cạm bẫy tạo đa instance trong Lite Mode nguy hiểm thế nào?
Cạm bẫy nguy hiểm nhất xảy ra khi một lập trình viên chuyển cấu hình sang proxyBeanMethods = false nhưng vẫn giữ thói quen gọi method @Bean trực tiếp trong thân hàm:
// SAI: Goi truc tiep trong Lite Mode sinh ra 3 instance doc lap
@Configuration(proxyBeanMethods = false)
public class CacheConfig {
@Bean
public KeyGenerator keyGenerator() { return new DefaultKeyGenerator(); }
@Bean
public LocalCache localCache() {
return new LocalCache(keyGenerator()); // Tao mot instance moi
}
@Bean
public RemoteCache remoteCache() {
return new RemoteCache(keyGenerator()); // Lai tao them mot instance moi nua!
}
}
Ứng dụng vẫn biên dịch và khởi động bình thường mà không hề báo lỗi, nhưng localCache và remoteCache lại dùng hai KeyGenerator hoàn toàn tách biệt.
Quy tắc bắt buộc trong Lite Mode: Luôn truyền bean phụ thuộc qua tham số của phương thức @Bean. Khi đó Spring sẽ tự động tiêm đúng singleton instance đã quản lý.
6. Sử dụng annotation @Import để ghép nối cấu hình dạng module ra sao?
Thay vì phụ thuộc vào việc quét gói tự động qua @ComponentScan, @Import cho phép bạn liên kết các lớp cấu hình một cách tường minh và chặt chẽ:
@Configuration
@Import({DatabaseConfig.class, SecurityConfig.class, MessagingConfig.class})
public class ApplicationConfig {
// Cau hinh tong the cua he thong
}
Spring Boot tận dụng tối đa @Import để nạp các lớp cấu hình tự động (Auto-configuration). Bằng cách khai báo tường minh danh sách lớp trong tệp META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, Spring Boot không cần tốn thời gian quét toàn bộ thư viện jar trong classpath.
7. Khai báo vòng đời bean qua initMethod và destroyMethod thế nào?
Với các đối tượng từ thư viện ngoài không implement InitializingBean hay DisposableBean, bạn có thể chỉ định tên hàm khởi tạo và hủy trực tiếp trong annotation @Bean:
@Bean(initMethod = "start", destroyMethod = "stop")
public NetworkServer networkServer() {
return new NetworkServer();
}
@Bean(destroyMethod = "") // Vo hieu hoa co che tu dong suy luan ham close/shutdown
public ExternalDataSource externalDataSource() {
return new ExternalDataSource();
}
Mặc định, Spring sẽ tự động tìm kiếm các phương thức mang tên close() hoặc shutdown() để gọi khi container đóng. Nếu bạn muốn tự quản lý việc dọn dẹp, hãy đặt destroyMethod = "".
Tóm tắt
- Phương thức
@Beanlà công cụ bắt buộc để cấu hình các lớp bên thứ ba không thể gắn@Component. @Configurationmặc định chạy ở Full Mode, dùng CGLIB tạo subclass để đảm bảo các lời gọi phương thức@Beannội bộ luôn trả về cùng một singleton instance.- Lite Mode (
proxyBeanMethods = false) không dùng CGLIB giúp khởi động nhanh hơn, nhưng bắt buộc phải tiêm phụ thuộc thông qua tham số hàm. - Lớp cấu hình Full Mode không được mang từ khóa
final, và các phương thức@Beankhông được làprivatehoặcfinal. @Importcung cấp cơ chế tổ chức và ghép nối cấu hình theo từng mô-đun tường minh và an toàn hơn@ComponentScan.
Tự kiểm tra
- Q1Tại sao Spring Container lại mặc định sử dụng CGLIB để tạo subclass cho các lớp được đánh dấu @Configuration?
- Q2Hậu quả nghiêm trọng nhất khi bạn gọi trực tiếp một phương thức @Bean khác bên trong thân hàm ở chế độ Lite Mode (proxyBeanMethods = false) là gì?
- Q3Ràng buộc kỹ thuật nào sau đây là BẮT BUỘC đối với lớp mang @Configuration ở chế độ Full Mode?
- Q4Tại sao Spring Boot lại ưu tiên sử dụng @Import thay vì @ComponentScan để nạp các lớp cấu hình tự động (Auto-configuration)?
- Q5Khi nào bạn nên cấu hình thuộc tính destroyMethod = "" trong khai báo @Bean?
Bài tiếp theo: @Conditional Bean: Nền Tảng Của Auto-Configuration
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Stereotype Và @ComponentScan: Cơ Chế Quét Và Đăng Ký Bean
Bài tiếp@Conditional Bean: Nền Tảng Của Auto-Configuration
Lộ trình Junior Java Backend Developer
Chặng 13/21 · Spring IoC, DI & Bean — bài 15/17 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này