Hãy hình dung Spring Boot như một người quản gia mở vali của bạn ra và tự bày căn phòng theo những gì thấy bên trong. Thấy có vợt tennis thì dọn sẵn lối ra sân; thấy bộ đồ nấu ăn thì bày bếp. Nhưng — và đây là chỗ tinh tế nhất — nếu bạn đã tự tay kê cái bàn, người quản gia lặng lẽ lùi lại, không đụng vào. Toàn bộ "auto-configuration" chỉ là người quản gia đó: nó nhìn những jar bạn mang theo, đoán bạn cần gì, dựng sẵn, và nhường ngay khi bạn muốn tự làm. Bài 1 đếm được 266 bean cho ba lớp của tôi. Bài này về nơi 263 bean còn lại đến từ.
Ý tưởng: đoán từ classpath
Spring Boot nhìn xem có những jar nào và quyết định cần gì:
Có spring-webmvc và Tomcat trên classpath → dựng máy chủ web nhúng, DispatcherServlet, bộ chuyển đổi JSON.
Có driver JDBC và thuộc tính spring.datasource.url → tạo DataSource với HikariCP.
Có spring-data-jpa → tạo EntityManagerFactory và quản lý giao dịch.
Đó là toàn bộ khái niệm "convention over configuration": bạn thêm một dependency, và mọi thứ tự nối vào.
Cơ chế: @Conditional
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
class DataSourceAutoConfiguration {
@Bean DataSource dataSource(...) { ... }
}
Bốn điều kiện hay gặp nhất — cũng chính là bốn câu người quản gia tự hỏi:
@ConditionalOnClass — chỉ chạy khi lớp đó có trên classpath. ("Trong vali có món này không?")
@ConditionalOnMissingBean — chỉ chạy khi bạn chưa tự khai bean đó. Đây là điều kiện quan trọng nhất: nó khiến mọi auto-configuration đều nhường bạn. ("Ngài đã tự kê bàn chưa?")
@ConditionalOnProperty — theo giá trị trong application.yml.
@ConditionalOnWebApplication — chỉ trong ứng dụng web.
Danh sách các lớp auto-configuration nằm trong tệp META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports của mỗi jar starter. Mở nó ra là thấy toàn bộ danh mục.
Xem Boot quyết định gì
java -jar app.jar --debug
Positive matches:
-----------------
AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
số auto-config KHỚP : 143
số dòng KHÔNG khớp : hàng trăm
143 lớp auto-configuration khớp cho một ứng dụng chỉ có web và actuator.
Báo cáo chia bốn phần:
Positive matches — đã áp dụng, kèm lý do. Negative matches — không áp dụng, kèm điều kiện nào không thoả. Exclusions — bạn loại trừ tường minh. Unconditional classes — luôn chạy.
Phần Negative matches là phần hữu ích nhất khi gỡ lỗi. Câu hỏi "vì sao bean X không được tạo" gần như luôn có câu trả lời ở đó:
DataSourceAutoConfiguration:
Did not match:
- @ConditionalOnProperty (spring.datasource.url) did not find property 'url'
Rõ ràng hơn nhiều so với việc đoán.
Có cách gọn hơn nếu đã bật actuator: endpoint /actuator/conditions trả về cùng thông tin dưới dạng JSON.
Ba cách can thiệp
Khai bean của bạn — nhờ @ConditionalOnMissingBean, auto-configuration tự lùi:
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper().registerModule(new JavaTimeModule());
}
Đây là cách sạch nhất, và cũng là lý do bạn hiếm khi phải tắt auto-configuration.
Chỉnh bằng thuộc tính — phần lớn auto-configuration đọc application.yml:
spring:
jackson:
default-property-inclusion: non_null
Cách này tốt hơn tự khai bean vì bạn giữ được mọi cấu hình mặc định khác.
Loại trừ hẳn:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
hoặc
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Dùng khi có starter kéo theo thứ bạn không muốn — ví dụ spring-boot-starter-data-jpa trong module chỉ dùng để chia sẻ entity.
Thứ tự
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
Auto-configuration chạy sau bean do bạn khai, và giữa chúng có thứ tự khai bằng after và before. Đây là lý do bạn không cần lo về thứ tự — Boot đã sắp sẵn.
Cái giá
143 lớp điều kiện phải được đánh giá lúc khởi động, và đó là phần đáng kể trong 1,1 giây ở bài 1.
Boot có vài cách giảm:
Chỉ thêm starter thật sự cần. spring-boot-starter-web kéo hơn bốn mươi jar; nếu bạn không cần web thì đừng thêm.
spring-boot-starter-webflux nhẹ hơn web nếu bạn dùng mô hình phản ứng.
AOT và Native Image tính sẵn điều kiện lúc build — bài 58 sẽ đo.
Với test, @SpringBootTest nạp toàn bộ context nên chậm. Các lát cắt như @WebMvcTest chỉ nạp phần cần — bài 52 sẽ nói.
Tự viết auto-configuration
Nếu bạn làm thư viện dùng chung trong công ty, viết auto-configuration cho nó là đáng:
@AutoConfiguration
@ConditionalOnClass(KhachHangClient.class)
@ConditionalOnProperty(prefix = "cty.khachhang", name = "url")
@EnableConfigurationProperties(KhachHangProps.class)
public class KhachHangAutoConfiguration {
@Bean
@ConditionalOnMissingBean
KhachHangClient khachHangClient(KhachHangProps p) {
return new KhachHangClient(p.url());
}
}
Khai tên lớp vào META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
Nhớ @ConditionalOnMissingBean — nó cho người dùng thư viện quyền ghi đè, và đó là quy ước bất thành văn của hệ sinh thái Spring.
Lần tới khi một bean "không hiểu sao không có", đừng đoán — hỏi thẳng người quản gia xem nó đã cân nhắc gì:
java -jar app.jar --debug 2>&1 | grep -A3 'Did not match' | head -40
Đọc mười điều kiện đầu không khớp, và bạn sẽ thấy Boot đang cân nhắc cả đống thứ mình không biết là tồn tại. Đây là chỗ tìm đầu tiên, trước khi ngồi đoán.
Mẫu số chung
"Framework tự cấu hình theo quy ước, bạn chỉ khai cái khác thường" — convention over configuration — không phải phát minh của Spring Boot, mà là một triết lý cả một thế hệ framework theo đuổi, mỗi nơi một liều lượng.
- Ruby on Rails là ông tổ của chính cụm từ đó: thư mục đặt đúng chỗ thì mọi thứ tự nối, gem tự đăng ký qua Railtie, và bạn ghi đè bằng cách khai tường minh — đúng mô hình "người quản gia lùi lại khi bạn tự bày" mà Boot kế thừa.
- Node/Express nằm ở cực ngược lại: gần như không có phép màu nào, bạn tự nối từng thứ bằng tay — đổi lại không bao giờ phải hỏi "cái này ở đâu ra", nhưng tốn nhiều mã khuôn mẫu. NestJS thì kéo hệ sinh thái Node về gần Spring với DI và module.
- ASP.NET Core đi đường giữa: builder tường minh cộng quy ước, cộng hosting startup assemblies tự phát hiện dịch vụ — có phép màu, nhưng bắt bạn thấy nó nhiều hơn Boot.
Và cái giá thì giống nhau ở mọi nơi: phép màu bạn không thấy = chi phí khởi động cộng câu hỏi "thứ này từ đâu ra" lúc gỡ lỗi. Điều tách framework tốt khỏi framework bực mình không phải là ít phép màu hơn, mà là có kèm một tấm gương để soi phép màu — --debug và /actuator/conditions của Boot, bin/rails about và danh sách initializer của Rails. Sợi chỉ chung đáng mang theo: convention-over-configuration đánh đổi sự tường minh lấy tốc độ dựng ban đầu, và nó chỉ lành mạnh khi đi kèm hai thứ — một báo cáo cho bạn nhìn thấy nó đã quyết gì, và một van thoát kiểu @ConditionalOnMissingBean để bạn giành lại quyền bất cứ lúc nào. Thiếu tấm gương, phép màu thành cái hộp đen; thiếu van thoát, nó thành cái lồng.
Ngày mai: đọc log khởi động Spring Boot — những dòng đáng đọc và những dòng nên tắt.