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ì:

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.

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:

@ConditionalOnClass — chỉ chạy khi lớp đó có trên classpath.

@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.

@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ó webactuator.

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 afterbefore. Đâ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.

Thử ba mươi giây

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. Bạn sẽ thấy Boot đang cân nhắc những thứ mình không biết là tồn tại — và lần tới khi một bean "không hiểu sao không có", đây là chỗ tìm đầu tiên.

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.