Auto-configuration & starters: Spring Boot tự cấu hình thế nào

4 câu hỏi~25 phútTừ khóa: @SpringBootApplication, @EnableAutoConfiguration, @ConditionalOnClass, @ConditionalOnMissingBean, AutoConfiguration.imports, starter, debug=true

Tiếp đi. Mình chỉ thêm dependency với vài dòng yml là có DataSource, sao Spring Boot biết mà tạo?

Câu hỏi đúng chỗ. Ba bài trước ta tự đánh @Service, @Bean. Nhưng shop-app còn dùng DataSource, EntityManagerFactory, ObjectMapper, Tomcat... mà ta chưa hề khai báo. Đó là auto-configuration.

1. Bài toán: shop-app cần kết nối PostgreSQL

Thời Spring "cổ điển" (chưa có Boot), bạn phải tự viết:

@Configuration
public class DbConfig {
    @Bean DataSource dataSource() { var ds = new HikariDataSource(); ds.setJdbcUrl(...); ... return ds; }
    @Bean LocalContainerEntityManagerFactoryBean emf(DataSource ds) { ... }
    @Bean PlatformTransactionManager txManager(EntityManagerFactory emf) { ... }
}

Cộng thêm Jackson, Tomcat, DispatcherServlet, mỗi project lặp lại vài trăm dòng giống nhau.

2. Vấn đề

3. Giải pháp: starter + auto-configuration + property

Nguyên tắc: convention over configuration, nhưng bạn luôn override được.

4. Code từng bước

Bước 1: Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

Bước 2: application.yml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/shop
    username: ${DB_USER}
    password: ${DB_PASSWORD}      # lấy từ biến môi trường, không hardcode
    hikari:
      maximum-pool-size: 10
debug: true                       # in Condition Evaluation Report khi start

Bước 3: Main class

@SpringBootApplication
public class ShopApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopApplication.class, args);
    }
}

@SpringBootApplication = ba annotation:

@SpringBootConfiguration   // = @Configuration
@EnableAutoConfiguration   // bật auto-config
@ComponentScan             // scan package com.example.shop và con

Bước 4: Kiểm tra

./mvnw spring-boot:run

============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource',
        'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'
   HibernateJpaAutoConfiguration matched: ...
Negative matches:
   MongoAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'com.mongodb.client.MongoClient'

Hoặc khi có Actuator: curl localhost:8080/actuator/conditions.

5. Cách hoạt động

@EnableAutoConfiguration
        |
        v
Đọc META-INF/spring/
  org.springframework.boot.autoconfigure.AutoConfiguration.imports
  (hàng trăm class: DataSourceAutoConfiguration, JacksonAutoConfiguration...)
        |
        v
Với mỗi class, đánh giá @Conditional*:
  DataSourceAutoConfiguration
    @ConditionalOnClass(DataSource.class)        ✔ có HikariCP
    @ConditionalOnMissingBean(DataSource.class)  ✔ bạn chưa tự khai báo
        |
        v
  Tạo HikariDataSource từ DataSourceProperties
  (bind spring.datasource.* trong application.yml)
        |
        v
  HibernateJpaAutoConfiguration thấy có DataSource → tạo EntityManagerFactory
        |
        v
  TransactionAutoConfiguration → JpaTransactionManager

Auto-config luôn chạy sau bean của bạn. Vì thế @ConditionalOnMissingBean nhìn thấy bean bạn tự khai báo và lùi lại.

6. So sánh các @Conditional hay gặp

AnnotationĐiều kiệnVí dụ
@ConditionalOnClassClass có trên classpathCó ObjectMapper → cấu hình Jackson
@ConditionalOnMissingBeanChưa có bean kiểu đóBạn tự tạo DataSource → Boot bỏ qua
@ConditionalOnBeanĐã có bean kiểu đóCó DataSource → tạo JdbcTemplate
@ConditionalOnPropertyProperty có giá trịshop.email.enabled=true
@ConditionalOnWebApplicationApp là webCấu hình DispatcherServlet

7. Khi nào nên dùng / không nên dùng

Vậy tự viết một starter như của Spring được không?

Được, và khá ngắn. Ví dụ shop-audit-spring-boot-starter cho mọi service trong công ty:

// 1. Properties
@ConfigurationProperties(prefix = "shop.audit")
public record AuditProperties(boolean enabled, String topic) { }

// 2. Auto-config
@AutoConfiguration
@ConditionalOnClass(AuditClient.class)
@ConditionalOnProperty(prefix = "shop.audit", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean          // service nào muốn tự định nghĩa thì cứ khai báo
    public AuditClient auditClient(AuditProperties props) {
        return new AuditClient(props.topic());
    }
}
# 3. src/main/resources/META-INF/spring/
#    org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.audit.AuditAutoConfiguration

Service nào chỉ cần thêm dependency là có bean AuditClient. Quy ước tên: starter của bên thứ ba đặt xxx-spring-boot-starter; tên spring-boot-starter-xxx dành cho Spring.

Lưu ý: file spring.factories cho auto-config là cách cũ (Boot 2). Boot 3 dùng file AutoConfiguration.imports.

Phỏng vấn hay hỏi "em đã làm feature nào liên quan tới Spring?". Trả lời sao?

Đừng liệt kê annotation. Interviewer muốn nghe bài toán → feature → vì sao, và họ sẽ đào sâu từ câu trả lời. Với shop-app có thể kể như sau:

Bài toán                          Feature Spring              Họ sẽ hỏi tiếp
---------------------------------------------------------------------------
Lắp ráp service, dễ test          IoC/DI, constructor inj.   Bean scope? 2 bean cùng kiểu?
Tạo đơn + trừ kho phải đồng bộ    @Transactional             Propagation? Self-invocation?
Gửi email không làm user chờ      @Async + executor riêng    Mất task khi crash? Exception?
Log thời gian / audit             AOP + custom annotation    JDK vs CGLIB proxy?
Kết nối DB nhanh                  Auto-config + starter JPA  @ConditionalOnMissingBean?

Chỉ chọn 2–3 cái bạn làm thật và hiểu sâu. Kể một cái bạn từng gặp bug (ví dụ @Transactional không rollback vì self-invocation) sẽ thuyết phục hơn liệt kê 10 annotation.

Phỏng vấn hỏi auto-configuration thì trả lời sao?

Q1. Spring Boot auto-configuration hoạt động thế nào?

🇻🇳 Tiếng Việt:

@SpringBootApplication bật @EnableAutoConfiguration. Boot đọc danh sách class auto-config trong file AutoConfiguration.imports của các jar. Mỗi class có điều kiện @Conditional, ví dụ có class trên classpath, chưa có bean, có property. Thỏa thì tạo bean với giá trị từ application.yml. Auto-config xử lý sau bean của người dùng nên bean tự khai báo sẽ được ưu tiên.

🇬🇧 English:

@SpringBootApplication enables @EnableAutoConfiguration. Boot reads the list of auto-configuration classes from AutoConfiguration.imports files in the jars. Each class is guarded by conditions such as a class on the classpath, a missing bean or a property value. If they match, it creates beans bound to application.yml. Auto-configuration runs after user configuration, so your own beans take precedence.

Q2. Làm sao biết vì sao một bean không được tạo?

🇻🇳 Tiếng Việt:

Bật debug=true để in Condition Evaluation Report, hoặc dùng endpoint /actuator/conditions. Report cho biết điều kiện nào match, điều kiện nào không và lý do.

🇬🇧 English:

Set debug=true to print the Condition Evaluation Report, or use the /actuator/conditions endpoint. It shows which conditions matched, which did not, and why.

Q3. Starter là gì? Viết starter riêng thế nào?

🇻🇳 Tiếng Việt:

Starter là dependency gom thư viện tương thích kèm auto-configuration. Viết riêng gồm: class @AutoConfiguration với các @Conditional, class @ConfigurationProperties, và đăng ký trong META-INF/spring/...AutoConfiguration.imports. Bean nên có @ConditionalOnMissingBean để service dùng có thể override.

🇬🇧 English:

A starter is a dependency that bundles compatible libraries together with auto-configuration. To write one, create an @AutoConfiguration class with conditions, a @ConfigurationProperties class, and register it in META-INF/spring/...AutoConfiguration.imports. Beans should use @ConditionalOnMissingBean so consumers can override them.

Cách nhớ ngắn gọn


Tự kiểm tra

@SpringBootApplication gồm những annotation nào?

@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan.

Tự khai báo @Bean DataSource thì Boot còn tạo HikariDataSource không?

Không. @ConditionalOnMissingBean thấy bean của bạn nên bỏ qua.

Class nằm ngoài package của main class có được scan không?

Không, trừ khi khai báo thêm scanBasePackages hoặc @Import.

Boot 3 đăng ký auto-config ở file nào?

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.

Vì sao password DB nên để ${DB_PASSWORD}?

Không commit secret vào git; giá trị lấy từ biến môi trường hoặc secret manager.