Auto-configuration & starters: Spring Boot tự cấu hình thế nào
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 đề
- Cấu hình boilerplate giống nhau ở mọi project.
- Phiên bản thư viện không khớp nhau (Hibernate vs Spring vs Jackson).
- Muốn đổi một chi tiết vẫn phải hiểu toàn bộ cấu hình.
3. Giải pháp: starter + auto-configuration + property
- Starter: một dependency gom sẵn các thư viện tương thích (
spring-boot-starter-data-jpa= Hibernate + HikariCP + Spring Data JPA). - Auto-configuration: các class
@AutoConfigurationcó điều kiện. Thấy thư viện trên classpath và bạn chưa tự khai báo bean thì Boot tạo giúp. - Property: tham số cho auto-config qua
application.yml(spring.datasource.url...).
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ện | Ví dụ |
|---|---|---|
@ConditionalOnClass | Class có trên classpath | Có ObjectMapper → cấu hình Jackson |
@ConditionalOnMissingBean | Chưa có bean kiểu đó | Bạn tự tạo DataSource → Boot bỏ qua |
@ConditionalOnBean | Đã có bean kiểu đó | Có DataSource → tạo JdbcTemplate |
@ConditionalOnProperty | Property có giá trị | shop.email.enabled=true |
@ConditionalOnWebApplication | App là web | Cấu hình DispatcherServlet |
7. Khi nào nên dùng / không nên dùng
- Mặc định: tin auto-config, chỉ chỉnh bằng property.
- Cần khác hẳn (2 DataSource, custom ObjectMapper): khai báo
@Beancủa bạn, auto-config tự lùi. - Tắt hẳn một auto-config:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class). - Đừng đoán: bật
debug=truehoặc/actuator/conditionsđể xem vì sao bean có/không có.
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:
@SpringBootApplicationbật@EnableAutoConfiguration. Boot đọc danh sách class auto-config trong fileAutoConfiguration.importscủ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:
@SpringBootApplicationenables@EnableAutoConfiguration. Boot reads the list of auto-configuration classes fromAutoConfiguration.importsfiles 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 toapplication.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ậtdebug=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:
Setdebug=trueto print the Condition Evaluation Report, or use the/actuator/conditionsendpoint. 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@AutoConfigurationvới các@Conditional, class@ConfigurationProperties, và đăng ký trongMETA-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@AutoConfigurationclass with conditions, a@ConfigurationPropertiesclass, and register it inMETA-INF/spring/...AutoConfiguration.imports. Beans should use@ConditionalOnMissingBeanso consumers can override them.
Cách nhớ ngắn gọn
- Starter = gói thư viện. Auto-config = bean có điều kiện. Property = tham số.
- Có class + thiếu bean → Boot tạo. Bạn khai báo → Boot lùi.
- Không hiểu vì sao?
debug=true. - Boot 3:
AutoConfiguration.imports, không cònspring.factories.
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.