Auto-configuration deep dive — bóc tách @EnableAutoConfiguration
Auto-configuration là magic chính của Spring Boot. Bài này bóc cơ chế ở mức bytecode: AutoConfigurationImportSelector, file AutoConfiguration.imports, 12 family annotation @ConditionalOn*, autoconfig ordering với @AutoConfigureBefore/After, debug autoconfig với /actuator/conditions, và viết autoconfig của riêng bạn.
Bài 02 đã chỉ ra Boot có 5 trụ cột — starter là 1, auto-configuration là 1 nữa. Starter chỉ pull jar; auto-config làm phần còn lại — biến jar trên classpath thành bean trong container, không cần code config.
Đây là bài quan trọng nhất Module 02. Sau bài này, khi bạn thêm 1 starter và thấy "magic happens" — bạn biết chính xác AutoConfigurationImportSelector đọc file nào, scan classpath ra sao, register bean theo điều kiện gì. Bạn debug được "tại sao bean này không tạo" trong 5 phút thay vì 5 giờ.
1. Auto-configuration giải quyết gì
Quay lại pain section 1.3 của bài 01 — Spring 4 yêu cầu mỗi project copy 30-100 dòng XML config:
<!-- applicationContext.xml — Spring 4 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="${db.url}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.pass}"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.olhub.domain"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<jpa:repositories base-package="com.olhub.repository"/>
Auto-config nói: "Bạn không cần viết 30 dòng đó. Tôi tự register DataSource, EntityManagerFactory, JpaTransactionManager nếu thấy Hibernate trên classpath và spring.datasource.url trong config."
Bạn chỉ cần:
# application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/app
username: app
password: secret
Và:
@SpringBootApplication
public class App { ... }
Boot làm còn lại. Đây là magic chính.
2. @EnableAutoConfiguration — entry point
@SpringBootApplication chứa @EnableAutoConfiguration:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
2 phần quan trọng:
@Import(AutoConfigurationImportSelector.class)— class này load tất cả autoconfig.@AutoConfigurationPackage— đánh dấu package của class app là root cho@EntityScan,@ComponentScan. Đây là lý do@SpringBootApplicationnên ở root package.
Phần thú vị là AutoConfigurationImportSelector. Nó implement DeferredImportSelector — interface Spring dùng để lazy register class config sau khi mọi @Configuration thông thường đã process.
3. AutoConfigurationImportSelector — đọc file AutoConfiguration.imports
Logic chính của selector (đơn giản hoá):
public class AutoConfigurationImportSelector implements DeferredImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
// Buoc 1: doc file AutoConfiguration.imports tu MOI jar trong classpath
List<String> configurations = ImportCandidates
.load(AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
// Buoc 2: bo nhung class user explicitly exclude
configurations = removeExclusions(configurations, exclusions);
// Buoc 3: filter qua @Conditional* — chi giu class match condition
configurations = filter(configurations, autoConfigurationMetadata);
return configurations.toArray(String[]::new);
}
}
3 bước cốt lõi: load → exclude → filter.
3.1 File AutoConfiguration.imports
Mỗi jar Boot autoconfig (vd spring-boot-autoconfigure.jar) có file:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
Mở file này (text thuần) — nó liệt kê fully-qualified class name của mọi autoconfig:
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
... 143 dong (Boot 3.4)
Spring Boot scan tất cả jar trên classpath cho file này. Mỗi jar có thể đóng góp autoconfig:
spring-boot-autoconfigure.jar— 143 autoconfig core của Boot.spring-cloud-config-client-*.jar— autoconfig của Spring Cloud.spring-ai-starter-*.jar— autoconfig của Spring AI.- Custom starter (bài 02 section 6) — bạn viết file này.
Trước Boot 2.7, dùng file META-INF/spring.factories với key EnableAutoConfiguration=.... Boot 3+ migrate sang AutoConfiguration.imports (file text đơn giản hơn) — backward compat vẫn đọc spring.factories.
flowchart TB
Cs["Classpath scan"]
F1["spring-boot-autoconfigure.jar<br/>AutoConfiguration.imports"]
F2["spring-cloud-*.jar<br/>AutoConfiguration.imports"]
F3["custom-starter.jar<br/>AutoConfiguration.imports"]
Merged["143 + N autoconfig<br/>candidates"]
Filter["@ConditionalOn* filter"]
Active["~30-50 autoconfig<br/>thuc su register"]
Cs --> F1
Cs --> F2
Cs --> F3
F1 --> Merged
F2 --> Merged
F3 --> Merged
Merged --> Filter
Filter --> Active
style Active fill:#d1fae53.2 Filter qua @Conditional*
143 autoconfig là candidate — không phải tất cả đều register. Mỗi autoconfig có 1+ @Conditional* annotation. Spring Boot evaluate condition tại runtime, chỉ register autoconfig pass tất cả condition.
Ví dụ DataSourceAutoConfiguration:
@AutoConfiguration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration { }
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({...})
protected static class PooledDataSourceConfiguration { }
}
DataSourceAutoConfiguration register chỉ khi:
DataSourceclass trên classpath (HikariCP/JDBC).- Không có
ConnectionFactorybean (đó là R2DBC reactive — nếu có thì user dùng reactive, không cần autoconfig JDBC).
Bên trong, 2 nested config:
- EmbeddedDatabaseConfiguration: nếu classpath có H2/Derby/HSQLDB và user chưa khai
DataSource→ tạo embedded DB cho dev. - PooledDataSourceConfiguration: nếu có HikariCP và user chưa khai
DataSource→ tạo Hikari pool.
Hệ quả thực tế: thêm spring-boot-starter-data-jpa + property spring.datasource.url → Boot tự register HikariDataSource, EntityManagerFactory, JpaTransactionManager. Bạn không viết dòng config nào.
4. Family @ConditionalOn* — 12 annotation
Bảng đầy đủ (mở rộng từ Module 01 bài 06):
| Annotation | Condition |
|---|---|
@ConditionalOnClass | Class này có trong classpath |
@ConditionalOnMissingClass | Class không có |
@ConditionalOnBean | Bean này đã register trong context |
@ConditionalOnMissingBean | Bean chưa register — fallback default |
@ConditionalOnProperty | Property có giá trị mong đợi |
@ConditionalOnExpression | SpEL expression true |
@ConditionalOnWebApplication | App là web (servlet/reactive) |
@ConditionalOnNotWebApplication | App không web |
@ConditionalOnResource | Resource tồn tại trên classpath |
@ConditionalOnJava | Java version match |
@ConditionalOnCloudPlatform | Cloud platform detect (CF, K8s, ...) |
@ConditionalOnJndi | JNDI resource available |
4.1 @ConditionalOnClass — phổ biến nhất
@AutoConfiguration
@ConditionalOnClass(RedisTemplate.class)
public class RedisAutoConfiguration { ... }
Cơ chế: Spring đọc bytecode autoconfig class, không thực sự load RedisTemplate. Lý do: nếu Redis không có, load class sẽ throw ClassNotFoundException → autoconfig fail trước khi check condition.
@ConditionalOnClass parameter có 2 dạng:
value = SomeClass.class— phải import được class. Risky.name = "fully.qualified.ClassName"— string, không cần import.
Best practice: dùng name cho autoconfig của starter, vì autoconfig phải compile được kể cả khi class kia không có classpath.
4.2 @ConditionalOnMissingBean — cơ chế "user override"
@AutoConfiguration
public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(); // default cua Boot
}
}
Logic: chỉ register cacheManager nếu user chưa register bean CacheManager. Đây là cách Boot "rút lui" khi user override.
@Configuration
public class UserCustomConfig {
@Bean
public CacheManager cacheManager() {
return new RedisCacheManager(...); // user custom
}
}
Có bean này → Boot autoconfig skip → user's bean win. Đây là contract chính của "opinionated, not lock-in".
@ConditionalOnMissingBean có 3 attribute:
value(default): bean class.name: bean name.type(string): tránh ClassNotFound nếu type không có classpath.
4.3 @ConditionalOnProperty — feature flag
@AutoConfiguration
@ConditionalOnProperty(
name = "feature.tracing.enabled",
havingValue = "true",
matchIfMissing = false
)
public class TracingAutoConfiguration { ... }
Register chỉ khi feature.tracing.enabled=true. matchIfMissing=false (default) → property phải có. Set matchIfMissing=true để autoconfig active mặc định.
Pattern phổ biến trong Boot autoconfig:
@ConditionalOnProperty(
prefix = "spring.jpa",
name = "open-in-view",
havingValue = "true",
matchIfMissing = true // default true
)
public class OpenEntityManagerInViewAutoConfiguration { ... }
Boot bật OSIV (open-session-in-view) by default. User tắt qua spring.jpa.open-in-view=false.
4.4 @ConditionalOnWebApplication
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class WebMvcAutoConfiguration { ... }
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class WebFluxAutoConfiguration { ... }
Boot detect web stack qua classpath:
- Có
spring-webmvc→ SERVLET. - Có
spring-webflux→ REACTIVE. - Cả 2 → SERVLET (ưu tiên).
- Không có → ANY = false → CLI app.
4.5 Combine condition
Nhiều @ConditionalOn* cùng class = AND logic. Tất cả phải pass:
@AutoConfiguration
@ConditionalOnClass(KafkaTemplate.class) // co Kafka classpath
@ConditionalOnProperty(name = "spring.kafka.bootstrap-servers") // co property
@ConditionalOnMissingBean(KafkaTemplate.class) // user chua khai
public class KafkaAutoConfiguration { ... }
3 điều kiện cùng pass → register.
5. Autoconfig ordering — @AutoConfigureBefore/After
Một số autoconfig phụ thuộc autoconfig khác. Vd JpaRepositoriesAutoConfiguration cần EntityManagerFactory đã được autoconfig tạo. Spring dùng 2 annotation:
@AutoConfiguration(after = HibernateJpaAutoConfiguration.class)
public class JpaRepositoriesAutoConfiguration { ... }
@AutoConfiguration(before = WebMvcAutoConfiguration.class)
public class HypermediaAutoConfiguration { ... }
@AutoConfigureAfter/Before set thứ tự evaluate — autoconfig sau xét sau, có cơ hội thấy bean đã được autoconfig trước register.
Ví dụ chain:
flowchart LR
DSC["DataSourceAutoConfiguration"]
HJC["HibernateJpaAutoConfiguration<br/>@AutoConfigureAfter(DSC)"]
JRC["JpaRepositoriesAutoConfiguration<br/>@AutoConfigureAfter(HJC)"]
DSC --> HJC --> JRC
style DSC fill:#fef3c7DSC chạy trước → tạo DataSource. HJC chạy sau → thấy DataSource rồi → tạo EntityManagerFactory. JRC chạy cuối → tạo Repository proxy dùng EntityManagerFactory.
Nếu thứ tự sai (vd JRC chạy trước HJC), @ConditionalOnBean(EntityManagerFactory.class) của JRC sẽ false → JRC skip → repository không tạo. Đây là lý do ordering quan trọng.
@AutoConfigureOrder cho fine-grained ordering với số:
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class EarlyAutoConfiguration { ... }
6. Debug autoconfig — Actuator /conditions
Khi autoconfig không hoạt động như mong đợi, đừng đoán — bật Actuator endpoint /actuator/conditions:
management:
endpoints:
web:
exposure:
include: health,conditions,beans,env,mappings
GET /actuator/conditions:
{
"contexts": {
"application": {
"positiveMatches": {
"DataSourceAutoConfiguration#dataSource": [
{"condition": "OnPropertyCondition", "message": "spring.datasource.url has value 'jdbc:postgresql://...'"},
{"condition": "OnClassCondition", "message": "DataSource class found"}
]
},
"negativeMatches": {
"RedisAutoConfiguration": {
"notMatched": [
{"condition": "OnClassCondition", "message": "RedisTemplate class not found"}
]
},
"KafkaAutoConfiguration": {
"notMatched": [
{"condition": "OnPropertyCondition", "message": "spring.kafka.bootstrap-servers not set"}
]
}
},
"exclusions": [],
"unconditionalClasses": [...]
}
}
}
3 section quan trọng:
positiveMatches: autoconfig đã register, kèm condition pass.negativeMatches: autoconfig skip, kèm lý do (cực hữu ích để debug "tại sao bean tôi không tạo").exclusions: autoconfig user explicitly exclude.
Cách dùng thực tế:
- Bug: "Redis cache không hoạt động."
- Curl
/actuator/conditions→ search "Redis". - Thấy
RedisAutoConfigurationtrongnegativeMatchesvới reason "RedisTemplate class not found". - → Quên thêm
spring-boot-starter-data-redis. Fix.
Cách thay thế: chạy app với flag --debug:
java -jar app.jar --debug
→ Boot in autoconfig report ra console khi start. Dùng cho dev local. Production bật Actuator.
7. Cơ chế evaluation tối ưu
143 autoconfig × N condition = nhiều check. Boot tối ưu bằng:
7.1 Caching condition
OnClassCondition cache kết quả Class.forName() — chỉ resolve 1 lần per class name.
7.2 Bulk class check qua bytecode
Boot scan class autoconfig bằng ASM (bytecode reader) — đọc annotation mà không load class. Lý do: load class → init static field → side effect. ASM chỉ đọc bytecode, no init.
7.3 Auto-configuration metadata
File META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports chỉ liệt kê fully-qualified name. Có thêm 1 file metadata:
META-INF/spring-autoconfigure-metadata.properties
File này contain pre-computed condition info — vd OnClassCondition của mỗi autoconfig — generate tại Boot build time. Spring Boot dùng metadata này để filter autoconfig sớm trước khi load class:
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration.ConditionalOnClass=\
org.apache.kafka.clients.producer.KafkaProducer
Boot đọc metadata → check KafkaProducer class có không — nếu không, skip autoconfig hoàn toàn (không load KafkaAutoConfiguration class). Đây là tối ưu lớn nhất — giảm autoconfig load từ 143 xuống ~30-50 cho app trung bình.
8. Viết autoconfig của bạn
Bài 02 section 6 đã giới thiệu cấu trúc — đây là chi tiết hoàn chỉnh:
8.1 TracingAutoConfiguration.java
package com.acme.tracing.autoconfigure;
@AutoConfiguration // = @Configuration + ordering
@ConditionalOnClass(Tracer.class) // chi register khi Tracer co classpath
@ConditionalOnProperty(
prefix = "acme.tracing",
name = "enabled",
matchIfMissing = true // bat default
)
@EnableConfigurationProperties(TracingProperties.class)
public class TracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean // user override duoc
public Tracer tracer(TracingProperties props) {
return Tracer.builder()
.endpoint(props.getEndpoint())
.serviceName(props.getServiceName())
.build();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Tracer.class) // chi tao filter neu co Tracer
public TracingWebFilter tracingFilter(Tracer tracer) {
return new TracingWebFilter(tracer);
}
}
8.2 TracingProperties.java
@ConfigurationProperties(prefix = "acme.tracing")
public class TracingProperties {
/** Enable/disable tracing. */
private boolean enabled = true;
/** Tracing collector endpoint. */
private String endpoint = "https://tracing.acme.internal:9411/api/v2/spans";
/** Logical service name. Required. */
private String serviceName;
/** Sampling rate 0.0-1.0. */
private double samplingRate = 1.0;
// getters/setters
}
8.3 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.acme.tracing.autoconfigure.TracingAutoConfiguration
(File text, mỗi dòng 1 class).
8.4 (Optional) META-INF/additional-spring-configuration-metadata.json
Để IDE autocomplete property:
{
"properties": [
{
"name": "acme.tracing.enabled",
"type": "java.lang.Boolean",
"description": "Enable/disable tracing for this service.",
"defaultValue": true
},
{
"name": "acme.tracing.endpoint",
"type": "java.lang.String",
"description": "Tracing collector endpoint URL.",
"defaultValue": "https://tracing.acme.internal:9411/api/v2/spans"
},
{
"name": "acme.tracing.service-name",
"type": "java.lang.String",
"description": "Logical service name for traces."
}
]
}
Hoặc dùng annotation processor spring-boot-configuration-processor — tự generate metadata từ Javadoc:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
9. Pitfall tổng hợp
❌ Nhầm 1: Tin "auto-config = magic không bóc được."
✅ Auto-config là Java class với annotation @Conditional*. Mở source bất kỳ autoconfig (vd DataSourceAutoConfiguration trên GitHub) đọc 1 lần — magic biến mất.
❌ Nhầm 2: Đoán "tại sao bean không tạo" thay vì check /actuator/conditions.
// Bug: Redis cache khong work
@Service public class S {
@Autowired CacheManager cm; // null hoac default ConcurrentMap thay Redis
}
✅ Curl /actuator/conditions → search Redis → đọc reason. 5 phút thay 5 giờ.
❌ Nhầm 3: Dùng @ConditionalOnClass(value = ThirdPartyClass.class) trong autoconfig của starter mà starter không pull ThirdPartyClass.
@ConditionalOnClass(KafkaTemplate.class) // KafkaTemplate co the khong co classpath
public class MyAutoConfig { ... }
Compile fail nếu starter không bring spring-kafka.
✅ Dùng @ConditionalOnClass(name = "org.springframework.kafka.core.KafkaTemplate") — string, không require import.
❌ Nhầm 4: Quên @ConditionalOnMissingBean cho default bean.
@Bean
public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); }
User register RedisCacheManager → conflict, ambiguous bean.
✅ Thêm @ConditionalOnMissingBean để Boot rút lui.
❌ Nhầm 5: Đặt autoconfig ở package mà bean không có Java import path đến class condition.
✅ Autoconfig nên ở package độc lập, không phụ thuộc package khác. Tham khảo cách Boot tổ chức: org.springframework.boot.autoconfigure.X.
❌ Nhầm 6: Quên file AutoConfiguration.imports.
✅ File này là bắt buộc — không có nó, autoconfig class không bao giờ load. Path chính xác: src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
❌ Nhầm 7: Override autoconfig bằng cách extends class autoconfig.
public class MyDataSourceConfig extends DataSourceAutoConfiguration { ... }
✅ Override bằng cách register @Bean DataSource của bạn — Boot tự rút lui qua @ConditionalOnMissingBean. Không bao giờ extends.
10. 📚 Deep Dive Spring Reference
Spring Boot Reference docs:
- Spring Boot Reference — Auto-configuration — overview chính thức.
- Spring Boot Reference — Creating Your Own Auto-configuration — hướng dẫn chính chủ viết autoconfig custom đầy đủ.
- Spring Boot Reference — Condition Annotations — đầy đủ family
@ConditionalOn*. - Spring Boot Reference — Configuration Property Metadata — generate metadata cho IDE autocomplete.
- Spring Boot Actuator — Conditions Endpoint — debug autoconfig.
Source code chính chủ:
AutoConfigurationImportSelector— class load autoconfig. Đọc methodselectImports.AutoConfiguration.imports— file liệt kê 143 autoconfig.DataSourceAutoConfiguration— autoconfig đại diện. Đọc 1 lần thấy pattern.OnClassCondition— implementation@ConditionalOnClass.
Bài viết:
- Stéphane Nicoll — Test slices for your Spring Boot autoconfigurations — testing autoconfig.
- Marten Deinum — Spring Boot Test Auto-configuration — viết autoconfig + test.
Tool quan trọng:
mvn dependency:tree— xem starter pull jar nào./actuator/conditions— runtime debug.--debugflag — startup autoconfig report.- IntelliJ IDEA "Spring Boot AutoConfiguration" tool window — visualize autoconfig graph.
Ghi chú: đọc source DataSourceAutoConfiguration 1 lần, sau đó mọi autoconfig khác sẽ "click". Tất cả Boot autoconfig follow cùng pattern: @AutoConfiguration + @ConditionalOn* + @EnableConfigurationProperties + @Bean @ConditionalOnMissingBean. Hiểu 1, hiểu hết.
11. Tóm tắt
- Auto-configuration là cơ chế Boot tự register bean dựa trên classpath + property — xoá 30-100 dòng XML config Spring 4.
- Entry point:
@EnableAutoConfiguration(trong@SpringBootApplication) →@Import(AutoConfigurationImportSelector.class). AutoConfigurationImportSelectorđọc fileMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importstừ mọi jar trên classpath.- Boot 3.4 có 143 autoconfig core; thêm starter (Cloud, AI, custom) đóng góp thêm.
- Mỗi autoconfig pass qua filter
@ConditionalOn*— chỉ register khi mọi condition true. - 12 family
@ConditionalOn*:OnClass,OnMissingClass,OnBean,OnMissingBean,OnProperty,OnExpression,OnWebApplication,OnNotWebApplication,OnResource,OnJava,OnCloudPlatform,OnJndi. @ConditionalOnMissingBeanlà cơ chế "user override default" — Boot tự rút lui khi user register bean.@AutoConfigureBefore/After/Orderquản thứ tự autoconfig evaluate — quan trọng cho dependency chain.- Debug autoconfig qua
/actuator/conditions(production) hoặc--debugflag (dev). Đừng đoán. - Tối ưu evaluation: ASM bytecode reader (no class load),
spring-autoconfigure-metadata.properties(pre-computed condition). - Viết autoconfig custom:
@AutoConfigurationclass +@ConfigurationProperties+ fileAutoConfiguration.imports+ (optional) configuration metadata cho IDE.
12. Tự kiểm tra
Q1Bạn add starter spring-boot-starter-data-redis, set spring.data.redis.host=localhost. Boot tự register RedisTemplate bean. Nhưng team thắc mắc "magic này hoạt động ra sao?". Giải thích chuỗi sự kiện từ classpath đến bean.▸
spring-boot-starter-data-redis, set spring.data.redis.host=localhost. Boot tự register RedisTemplate bean. Nhưng team thắc mắc "magic này hoạt động ra sao?". Giải thích chuỗi sự kiện từ classpath đến bean.Chuỗi 6 bước:
- Maven pull starter:
spring-boot-starter-data-redispullspring-data-redis,lettuce-core(Redis client),spring-boot-autoconfigure. - App start,
SpringApplication.run()triggerrefresh(): bước 5 (invokeBeanFactoryPostProcessors) chạyConfigurationClassPostProcessor, parse@SpringBootApplicationtrên classApp. @EnableAutoConfigurationtriggerAutoConfigurationImportSelector: selector load fileAutoConfiguration.importstừ mọi jar — bao gồm jarspring-boot-autoconfigurechứaRedisAutoConfigurationtrong list.- Filter qua condition:
@ConditionalOnClass(RedisTemplate.class)→RedisTemplatecó classpath (từspring-data-redis) → pass.@ConditionalOnMissingBean(RedisTemplate.class)→ user chưa register → pass.RedisAutoConfigurationregister.
- Trong
RedisAutoConfigurationcó@EnableConfigurationProperties(RedisProperties.class)→ bindspring.data.redis.*property vàoRedisProperties. - Bean
@Bean RedisTemplatetạo vớiRedisProperties.host=localhost.LettuceConnectionFactoryconnect đến Redis.
Verify chuỗi: bật /actuator/conditions → tìm RedisAutoConfiguration trong positiveMatches → thấy condition nào pass.
Bài học: không có magic. Chuỗi rõ ràng từ classpath → autoconfig load → condition filter → bean register.
Q2Đoạn sau có gì sai? Khi nào sai sẽ lộ?@AutoConfiguration
@ConditionalOnClass(KafkaTemplate.class)
public class MyKafkaConfig {
@Bean
public KafkaProducer myProducer(KafkaTemplate<String, String> template) {
return new KafkaProducer(template);
}
}
▸
@AutoConfiguration
@ConditionalOnClass(KafkaTemplate.class)
public class MyKafkaConfig {
@Bean
public KafkaProducer myProducer(KafkaTemplate<String, String> template) {
return new KafkaProducer(template);
}
}2 vấn đề:
- Compile-time risk: autoconfig file viết
@ConditionalOnClass(KafkaTemplate.class). ClassKafkaTemplatephải có trong classpath khi compile autoconfig. Nếu starter của bạn không pullspring-kafka→ compile fail.Fix: dùng
nameattribute (string):@ConditionalOnClass(name = "org.springframework.kafka.core.KafkaTemplate")Compile OK kể cả khi
KafkaTemplatekhông có. Runtime evaluate quaClass.forName(). - Thiếu
@ConditionalOnMissingBean: nếu user registermyProducerbean của họ, ambiguous → Spring throwNoUniqueBeanDefinitionException. Boot autoconfig **luôn** nên có@ConditionalOnMissingBeanđể rút lui khi user override.Fix:
@Bean @ConditionalOnMissingBean public KafkaProducer myProducer(KafkaTemplate<String, String> template) { ... }
Khi nào sai sẽ lộ:
- Vấn đề 1: lúc
mvn compileautoconfig module — fail nếu missing dependency. - Vấn đề 2: lúc service team override bean → conflict tại startup runtime.
Best practice: mọi autoconfig public-facing dùng name string + @ConditionalOnMissingBean. Verify với test integration: 1 test với class trên classpath, 1 test không có.
Q3Bạn debug "tại sao Spring Cache không hoạt động" dù đã thêm spring-boot-starter-cache + @EnableCaching. Cách đúng để diagnose?▸
spring-boot-starter-cache + @EnableCaching. Cách đúng để diagnose?Quy trình debug — KHÔNG đoán, dùng tool:
- Bật Actuator
/actuator/conditions:Curlmanagement.endpoints.web.exposure.include: health,conditions management.endpoint.conditions.show-values: alwayshttp://localhost:8080/actuator/conditions, search "Cache". - Đọc
positiveMatchesvànegativeMatches:- Nếu
CacheAutoConfigurationtrongnegativeMatchesvới reason "no Cache provider found" → thiếu cache provider (Caffeine/Redis/EhCache). Fix: thêmspring-boot-starter-cache+ provider lib. - Nếu trong
positiveMatchesnhưng cache không work → vấn đề khác (vd@Cacheabletrên private method, hoặc method called từ same class).
- Nếu
- Verify bean
CacheManagertồn tại: curl/actuator/beanssearchcacheManager. Nếu không có → autoconfig skip. - Bật log DEBUG cho cache:Spring sẽ log mọi
logging.level.org.springframework.cache: DEBUG@Cacheablehit/miss. - Verify
@EnableCachingtrên@Configurationclass (hoặc class với@SpringBootApplication). - Verify proxy mode:
@Cacheablechỉ work qua proxy. Self-call (this.cachedMethod()) bypass proxy → cache không invoke. Module 01 bài 04.
Sai lầm phổ biến — đừng làm:
- "Thử thêm
@EnableCaching(proxyTargetClass = true) coi sao" — guess thay debug. - "Restart app coi may không" — không deterministic.
- "Add log
System.out.printlntrong method" — không xác minh autoconfig.
Quy tắc: autoconfig issue → /actuator/conditions trước, mọi cách khác sau.
Q4Đoạn sau có 2 autoconfig. Thứ tự đăng ký bean ra sao? Bean nào được tạo, bean nào skip?@AutoConfiguration
public class A1 {
@Bean
public Foo foo() { return new Foo("from-A1"); }
}
@AutoConfiguration(after = A1.class)
public class A2 {
@Bean
@ConditionalOnMissingBean
public Foo foo() { return new Foo("from-A2"); }
@Bean
@ConditionalOnBean(Foo.class)
public Bar bar(Foo foo) { return new Bar(foo); }
}
▸
@AutoConfiguration
public class A1 {
@Bean
public Foo foo() { return new Foo("from-A1"); }
}
@AutoConfiguration(after = A1.class)
public class A2 {
@Bean
@ConditionalOnMissingBean
public Foo foo() { return new Foo("from-A2"); }
@Bean
@ConditionalOnBean(Foo.class)
public Bar bar(Foo foo) { return new Bar(foo); }
}Thứ tự evaluate: A1 trước (do @AutoConfiguration(after = A1.class) trên A2 ép A2 sau).
Kết quả:
- A1 register →
foobean ="from-A1". - A2 evaluate:
foo()trong A2 có@ConditionalOnMissingBean→ A1 đã registerfoo→ SKIP. Beanfoogiữ"from-A1".bar()có@ConditionalOnBean(Foo.class)→ A1 đã tạofoo→ register.barnhận"from-A1"Foo.
Output cuối:
- 1 bean
foo=Foo("from-A1") - 1 bean
bar=Bar(foo("from-A1"))
Nếu bỏ @AutoConfiguration(after = A1.class) → thứ tự không xác định. Có thể A2 evaluate trước A1:
fooA2 register (vì A1 chưa chạy →@ConditionalOnMissingBeanpass).- A1 evaluate sau →
foo()A1 không có@ConditionalOnMissingBean→ throwBeanDefinitionOverrideException(Boot 2.1+ mặc định cấm override).
Bài học:
@ConditionalOnMissingBeanphụ thuộc thứ tự — autoconfig đến trước win.@AutoConfigureBefore/Afterđảm bảo deterministic ordering.- Mọi
@Beandefault (registered by autoconfig) nên có@ConditionalOnMissingBean— best practice.
Q5Vì sao Boot dùng ASM bytecode reader thay vì Class.forName() để check @ConditionalOnClass? Lợi ích cụ thể?▸
Class.forName() để check @ConditionalOnClass? Lợi ích cụ thể?Lý do chính: tránh load class chưa cần thiết → tránh init static field → tránh side effect ngoài ý muốn.
Vấn đề với Class.forName():
- Khi load class, JVM init static field, static block, ... → có thể trigger DB connect, file IO, log.
- Class load 1 lần stuck trong ClassLoader cache — không unload được.
- Nếu class fail load (vd dependency missing), exception thrown → autoconfig fail thay skip.
- Performance: load 143 autoconfig class + dependency của chúng = startup chậm.
Cách Boot dùng ASM:
- ASM đọc bytecode autoconfig class (không load).
- Parse annotation table để extract
@ConditionalOnClassvalue. - Check class name có trong classpath qua
ClassLoader.getResource()(chỉ check file.classtồn tại, không load). - Nếu pass condition → load autoconfig class qua
Class.forName()bình thường. - Nếu fail → skip, không load.
Lợi ích cụ thể:
- Startup nhanh hơn: ~30-50% bean autoconfig skip không load → giảm class loading overhead.
- Memory thấp hơn: class skip không vào metaspace.
- No side effect: autoconfig fail load không trigger init code.
- Native image friendly: ASM bytecode analysis tương thích GraalVM AOT compilation.
Kết hợp với spring-autoconfigure-metadata.properties (pre-computed condition info) — Boot biết "skip class này" thậm chí trước khi đọc bytecode → tối ưu thêm 1 lớp.
Verify: bật --debug, count autoconfig "negative match" — đó là class Boot skip không load nhờ ASM filter.
Q6Team enterprise chuyển từ "starter custom + autoconfig" sang đơn giản hoá thành "@Import trong app". So sánh 2 approach. Khi nào nên dùng cái nào?▸
@Import trong app". So sánh 2 approach. Khi nào nên dùng cái nào?Approach 1 — Starter + autoconfig (recommend cho lib reusable):
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-tracing-spring-boot-starter</artifactId>
</dependency>
# application.yml
acme.tracing:
enabled: true
service-name: order-serviceApproach 2 — @Import explicit (đơn giản):
@SpringBootApplication
@Import(TracingConfig.class)
public class App { ... }
@Configuration
public class TracingConfig {
@Bean
public Tracer tracer() {
return Tracer.builder()...build();
}
}| Aspect | Starter + autoconfig | @Import |
|---|---|---|
| Ergonomics | Add 1 dep, set property | Add @Import trong code app |
| Conditional | Có (via @ConditionalOn*) | Không (manual check) |
| Default + override | Tự nhiên (@ConditionalOnMissingBean) | Phải code thủ công |
| IDE autocomplete property | Có (configuration metadata) | Không |
Actuator /conditions visibility | Có | Không |
| Setup overhead | 2 module (starter + autoconfig) | 0 — chỉ class config |
| Versioning | Bump starter version | Không có version concept |
Khi dùng cái nào:
- Starter + autoconfig: lib internal share giữa 5+ service team. Standardization quan trọng. Cần feature flag, conditional, default + override. Cần versioning để rollout dần.
@Import: config dùng trong 1 app. Hoặc lib nhỏ trong 1 monorepo. Hoặc prototype/early-stage chưa cần extract.
Quy tắc thực tế: bắt đầu với @Import. Khi thấy 3+ service copy-paste cùng config → extract thành starter. Đừng over-engineer ngay từ đầu.
Q7Boot 3.4 có 143 autoconfig core. Tại 1 app điển hình, chỉ ~30-50 autoconfig active. Phần còn lại đi đâu? Tại sao Boot vẫn load 143 nếu chỉ 30 active?▸
"Đi đâu": 100+ autoconfig còn lại nằm trong negativeMatches của /actuator/conditions — Boot evaluate condition, fail, skip.
Vì sao Boot "load" 143: chính xác hơn — Boot **không thực sự load** class autoconfig nếu condition đầu fail. Nhờ 2 lớp tối ưu:
- Pre-filter qua
spring-autoconfigure-metadata.properties: file metadata chứa pre-computed condition info. Boot đọc metadata, filter sớm autoconfig nào condition fail rõ ràng (vd@ConditionalOnClassvới class không có classpath). Loại 50-80 autoconfig trước khi load class. - ASM bytecode reader: đọc annotation autoconfig class qua bytecode (không load class). Filter thêm 30-50 autoconfig.
- Còn lại ~30-50 autoconfig: condition phức tạp cần evaluate runtime → load class, run condition. Pass → register bean. Fail → skip.
Performance impact:
- Không có 2 tối ưu trên: 143 class load → ~3-5s startup chỉ cho phase autoconfig.
- Có tối ưu: ~500ms cho phase autoconfig. Phần lớn startup nằm ở instantiate bean (bước 11 refresh).
Verify: /actuator/startup endpoint (Boot 3+) breakdown thời gian từng bean. Phase "load autoconfig metadata" thường <100ms thậm chí với 143 autoconfig.
Bài học: 143 candidates không có nghĩa là 143 bean. Chỉ subset condition pass mới register. Đó là design intentional — Boot có nhiều default sẵn, user opt-in qua starter/property.
Bài tiếp theo: application.properties vs application.yml — externalized configuration sâu
Bài này có giúp bạn hiểu bản chất không?
Bình luận (0)
Đang tải...