Auto-configuration là phần của Spring Boot vẫn còn giống như phép thuật ngay cả khi bạn đã hiểu hết mọi thứ còn lại. Bạn thêm một starter, thế là có DispatcherServlet, có JSON mapper, có Tomcat — trong khi không dòng nào trong thư mục source của bạn yêu cầu những thứ đó.
Nhưng ở đây không có phép thuật nào cả. Một auto-configuration chỉ là một @Configuration class chứa đầy các @Bean method — đúng loại class bạn đã viết suốt năm bài vừa rồi — kèm các annotation nói rõ khi nào nó được áp dụng. Bài này mở hộp ra: Boot lấy danh sách từ đâu, một class như thế trông thế nào, condition được đánh giá ra sao, và cách đọc báo cáo ghi lại từng quyết định của nó.
![]()
Mọi thứ bên dưới chạy trên OpenJDK 21.0.6 với Spring Boot 4.1.1 (Spring Framework 7.0.9, embedded Tomcat 11.0.24) và Gradle 9.7.1, trên project sinh bởi Spring Initializr với dependencies=web. Mọi con số, nội dung file và đoạn báo cáo đều copy từ project đó ra, không viết theo trí nhớ.
Auto-configuration là @Configuration class biết tự rút lui
Bạn đã biết bean là gì, registry giữ nó ra sao, và một @Bean method trong @Configuration class làm gì. Auto-configuration chỉ thêm đúng một ý: bản thân class và từng @Bean method của nó mang theo condition, và chúng bị bỏ qua khi condition không thỏa.
Đó là toàn bộ cơ chế. Boot phát hành các class này rải khắp các module của nó, đưa cho container những cái khớp condition, và bỏ phần còn lại. Chúng là configuration Spring bình thường — không có hỗ trợ đặc biệt nào từ container, không có lifecycle riêng, framework không đối xử với chúng khác gì một @Configuration class do bạn tự viết.
Phần kỹ thuật đáng quan tâm nằm ở ba câu hỏi, và phần còn lại của bài trả lời lần lượt: Boot dựng danh sách candidate bằng cách nào, condition được đánh giá ra sao, và làm sao biết chuyện gì đã xảy ra.
Spring Boot tìm auto-configuration bằng cách nào
@SpringBootApplication là annotation ghép, và một trong ba thành phần của nó là @EnableAutoConfiguration. Annotation đó tự nó không làm gì; nó là một @Import:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {AutoConfigurationImportSelector mới là nơi danh sách được tạo ra. Method cốt lõi của nó, lấy nguyên văn từ source 4.1.1:
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);Năm bước đọc thẳng từ trên xuống: lấy candidate, bỏ trùng, loại phần bạn exclude, lọc nhanh, phát sự kiện kết quả. getCandidateConfigurations ủy quyền cho ImportCandidates.load, và đây là đoạn đáng thuộc lòng:
private static final String LOCATION = "META-INF/spring/%s.imports";
...
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}findUrlsInClasspath chính là classLoader.getResources(location). Nó trả về mọi file khớp trên classpath, và danh sách candidate là hợp của tất cả. Vậy file cần tìm trong bất kỳ jar nào là:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
Đó là file text thuần, mỗi dòng một tên class đầy đủ. Đây là toàn bộ nội dung file nằm trong spring-boot-autoconfigure-4.1.1.jar, lấy ra bằng cách unzip jar trong Gradle cache:
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
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.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfigurationMười hai dòng. Con số này gây bất ngờ nếu bạn từng đọc bài hướng dẫn nào viết trước 2025, vì trên Spring Boot 3 file duy nhất này chứa gần như toàn bộ — cũng file đó trong spring-boot-autoconfigure-3.5.6.jar liệt kê 156 class. Boot 4 đã tách auto-configuration ra khỏi spring-boot-autoconfigure thành nhiều module spring-boot-* nhỏ, mỗi module mang file .imports riêng. Quét cả 39 jar trên runtime classpath của project này tìm thấy sáu file:
| Jar | Số candidate đóng góp |
|---|---|
spring-boot-autoconfigure-4.1.1.jar | 12 |
spring-boot-webmvc-4.1.1.jar | 6 |
spring-boot-servlet-4.1.1.jar | 5 |
spring-boot-tomcat-4.1.1.jar | 5 |
spring-boot-jackson-4.1.1.jar | 1 |
spring-boot-http-converter-4.1.1.jar | 1 |
| Tổng | 30 |
Vậy danh sách candidate của một web application tối giản trên Boot 4.1.1 là 30 class, không phải mười hai class trong cái jar mà cái tên gợi ý rằng nó sở hữu tất cả. Chỉ trích dẫn mười hai dòng đó là cách nhanh nhất để tự hiểu sai cơ chế này.
Một điểm cần đính chính so với tài liệu cũ. Cơ chế ngày xưa là một key tên EnableAutoConfiguration nằm trong META-INF/spring.factories; không jar nào trên classpath này còn mang key đó, và Boot 4 chỉ đọc các file .imports. spring.factories chưa biến mất — spring-boot-autoconfigure vẫn còn một file — nhưng thứ còn sót trong đó là listener, failure analyzer và import filter, không phải danh sách candidate.
Bộ lọc chạy trước khi đánh giá bất kỳ condition nào
getConfigurationClassFilter().filter(configurations) ở đoạn code trên chạy trước khi Spring load bất kỳ candidate class nào. Việc của nó là loại candidate một cách rẻ tiền, vì load 30 class (hoặc vài trăm, với application lớn) chỉ để phát hiện phần lớn không áp dụng được là một chi phí startup có thật.
Các filter được đăng ký trong spring.factories:
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationConditionChúng trả lời dựa trên một file sinh tự động khác, META-INF/spring-autoconfigure-metadata.properties, do annotation processor của Boot ghi lúc build. Đây là chỉ mục phẳng của các condition mà mỗi class khai báo:
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration.ConditionalOnClass=org.aspectj.weaver.Advice
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration=
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration.ConditionalOnClass=org.springframework.jmx.export.MBeanExporterNhờ đó filter kết luận được "org.aspectj.weaver.Advice không có trên classpath nên AspectJAutoProxyingConfiguration bị loại" chỉ bằng cách so một chuỗi, không cần đọc file class nào. Chỉ những candidate sống sót mới được load và đánh giá đầy đủ tập @ConditionalOn*.
Một auto-configuration thật trông thế nào
Lấy một class nhỏ trong spring-boot-servlet-4.1.1.jar. HttpEncodingAutoConfiguration gói trọn cả pattern trong bốn mươi dòng, đã bỏ phần header giấy phép:
package org.springframework.boot.servlet.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.CharacterEncodingFilter;
@AutoConfiguration
@EnableConfigurationProperties(ServletEncodingProperties.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnBooleanProperty(name = "spring.servlet.encoding.enabled", matchIfMissing = true)
public final class HttpEncodingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
CharacterEncodingFilter characterEncodingFilter(ServletEncodingProperties properties) {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(properties.getCharset().name());
filter.setForceRequestEncoding(properties.shouldForce(ServletEncodingProperties.HttpMessageType.REQUEST));
filter.setForceResponseEncoding(properties.shouldForce(ServletEncodingProperties.HttpMessageType.RESPONSE));
return filter;
}
}Đọc các annotation theo thứ tự là thấy ngay thiết kế:
| Annotation | Quyết định điều gì |
|---|---|
@AutoConfiguration | đây là một auto-configuration; nó được meta-annotate bằng @Configuration(proxyBeanMethods = false) và mang các thuộc tính sắp thứ tự |
@EnableConfigurationProperties | bind spring.servlet.encoding.* vào một properties object và đăng ký object đó thành bean |
@ConditionalOnWebApplication(type = SERVLET) | bỏ qua cả class trừ khi đây là servlet web application |
@ConditionalOnClass(CharacterEncodingFilter.class) | bỏ qua trừ khi class đó có trên classpath |
@ConditionalOnBooleanProperty(...) | bỏ qua nếu ai đó set property thành false; matchIfMissing = true nghĩa là mặc định bật |
@ConditionalOnMissingBean | đặt trên @Bean method: bỏ qua riêng bean này nếu registry đã có một CharacterEncodingFilter |
Ba condition ở mức class quyết định được ăn cả ngã về không cho toàn class. Condition ở mức method thì tính riêng từng bean, và chính điều đó cho phép một class có mười @Bean method vẫn đóng góp chín cái, để bạn tự cung cấp cái thứ mười.
Bản thân @Bean method chẳng có gì đặc biệt: tạo object, set vài field từ properties đã bind, rồi return. Nếu bạn tự viết nó trong @Configuration class của mình thì nó cũng y hệt như vậy.
Họ annotation @ConditionalOn*
Ở đây có hai tầng và nên tách bạch chúng.
@Conditional và interface Condition là tính năng của Spring Framework. Chúng không liên quan gì tới Boot: bất kỳ @Configuration class, @Bean method hay @Component nào cũng có thể mang @Conditional(SomeCondition.class), và Spring sẽ hỏi condition đó trước khi đăng ký bean definition.
Các annotation @ConditionalOn* là của Spring Boot. Mỗi cái là một @Conditional đi kèm sẵn một Condition implementation, đóng gói lại để bạn viết annotation thay vì viết class. Boot 4.1.1 có 21 annotation như vậy trong org.springframework.boot.autoconfigure.condition. Bảy cái dưới đây bao gần hết những gì bạn sẽ đọc thấy trong báo cáo:
| Annotation | Khớp khi |
|---|---|
@ConditionalOnClass | mọi class được nêu đều có trên classpath |
@ConditionalOnMissingClass | không class nào trong số được nêu có trên classpath |
@ConditionalOnBean | registry đã có bean thuộc type hoặc tên đó |
@ConditionalOnMissingBean | registry chưa có bean thuộc type hoặc tên đó |
@ConditionalOnProperty | property được set và khớp havingValue (có matchIfMissing cho trường hợp mặc định) |
@ConditionalOnResource | resource được nêu tồn tại, ví dụ classpath:schema.sql |
@ConditionalOnWebApplication | application là web application thuộc Type đã nêu — SERVLET, REACTIVE hoặc ANY |
Mười bốn cái còn lại hẹp hơn: @ConditionalOnBooleanProperty và @ConditionalOnProperties là biến thể chuyên biệt của cái về property, @ConditionalOnSingleCandidate khớp khi chỉ có đúng một bean ứng viên của một type, @ConditionalOnExpression nhận một SpEL expression, và còn các cái kiểm tra Java version, threading model, JNDI, WAR deployment và cloud platform.
Đây là chúng đặt trên một configuration class của riêng tôi trong project demo, để hành vi quan sát được thay vì chỉ mô tả:
@Configuration
public class ConditionsDemo {
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
String onClass() { return "onClass"; }
@Bean
@ConditionalOnMissingClass("com.google.gson.Gson")
String onMissingClass() { return "onMissingClass"; }
@Bean
@ConditionalOnBean(DispatcherServlet.class)
String onBean() { return "onBean"; }
@Bean
@ConditionalOnProperty(name = "demo.audit", havingValue = "on")
String onProperty() { return "onProperty"; }
@Bean
@ConditionalOnResource(resources = "classpath:application.properties")
String onResource() { return "onResource"; }
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
String onWebApplication() { return "onWebApplication"; }
}Chạy application rồi hỏi context xem bean nào tồn tại:
onClass = true
onMissingClass = true
onBean = false
onProperty = false
onResource = true
onWebApplication = trueNăm trong sáu kết quả đúng như dự đoán: Tomcat có trên classpath, Gson thì không, application.properties tồn tại, đây là servlet application, và demo.audit chưa được set. Thêm --demo.audit=on vào command line thì cái thứ tư lật thành true. Kết quả thứ năm mới là chỗ thú vị.
Cái bẫy: @ConditionalOnBean trên configuration của chính bạn
onBean là false, trong khi context hoàn chỉnh chắc chắn có bean dispatcherServlet — application đang phục vụ HTTP request mà. Condition đã được đánh giá vào thời điểm bean đó chưa tồn tại.
Các @Configuration class của người dùng được parse trước, trong lúc component scanning. Auto-configuration được import bởi một DeferredImportSelector, mà theo thiết kế thì chạy sau cùng, khi mọi thứ bạn viết đã được biết. Nên lúc ConditionsDemo được parse, DispatcherServletAutoConfiguration chưa chạy và dispatcherServlet chưa được đăng ký.
Sự bất đối xứng đó là lý do tài liệu ghi rằng @ConditionalOnBean và @ConditionalOnMissingBean chỉ nên dùng bên trong auto-configuration class. Trong auto-configuration chúng đáng tin, vì tới lúc đó mọi bean của người dùng đã đăng ký xong. Trong configuration của bạn, chúng phụ thuộc thứ tự parse mà bạn không kiểm soát được. Hãy dùng @ConditionalOnClass hoặc @ConditionalOnProperty trong code của mình, và để các condition về sự tồn tại bean lại cho auto-configuration.
Tự viết một Condition
Khi không annotation nào trong 21 cái kia vừa vặn, bạn xuống tầng interface bên dưới. Condition có đúng một method:
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);ConditionContext cho bạn Environment, BeanDefinitionRegistry, ConfigurableListableBeanFactory, ResourceLoader và ClassLoader — đủ mọi thứ để soi application ngay lúc nó đang được dựng. AnnotatedTypeMetadata mô tả chính cái đang được đánh giá: một class, hoặc một @Bean method, cùng các annotation của nó. Đây là một condition dựa trên kiến trúc CPU, thứ mà không annotation dựng sẵn nào phủ:
public class OnArm64Condition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String arch = context.getEnvironment().getProperty("os.arch", "unknown");
boolean match = arch.contains("aarch64") || arch.contains("arm64");
String target = (metadata instanceof MethodMetadata m)
? m.getDeclaringClassName() + "#" + m.getMethodName()
: metadata.toString();
System.out.println("[OnArm64Condition] " + target + " os.arch=" + arch + " -> " + match);
return match;
}
}@Configuration
public class ConditionDemoConfig {
@Bean
@Conditional(OnArm64Condition.class)
String cpuNote() { return "running on arm64"; }
}Trên máy này, một chiếc Mac Apple Silicon:
[OnArm64Condition] com.example.demo.ConditionDemoConfig#cpuNote os.arch=aarch64 -> true
...
cpuNote bean present: trueGhi đè property trên command line bằng --os.arch=x86_64 chứng minh chính condition đang làm việc chứ bean không được đăng ký vô điều kiện:
[OnArm64Condition] com.example.demo.ConditionDemoConfig#cpuNote os.arch=x86_64 -> false
...
cpuNote bean present: falseMột điều nên biết trước khi bạn đi tìm: một Condition thuần không xuất hiện trong báo cáo --debug. Báo cáo của Boot được điền bởi SpringBootCondition, base class mà mọi implementation @ConditionalOn* kế thừa. OnArm64Condition của tôi implement thẳng Condition, nên nó quyết định đúng mà không báo cáo gì — đó cũng là lý do nó tự in ra dòng của mình ở trên.
Back-off: Boot chỉ cấu hình phần bạn chưa làm
@ConditionalOnMissingBean là annotation làm cho toàn bộ thiết kế này chạy được. Nó cho phép Boot phát hành hàng trăm bean đầy quan điểm mà không bao giờ tranh chấp với bạn: ngay khi bạn khai báo một bean cùng type, bean của Boot ngừng được tạo. Không có gì để tắt, không property nào phải set, không cuộc đua ưu tiên nào phải thắng. Khai báo bean chính là cách override.
Đây là màn thật. Application là project sinh tự động, thêm một ApplicationRunner in ra những gì có trong registry. Trước tiên là bản nguyên vẹn:
CharacterEncodingFilter beans: [characterEncodingFilter]
characterEncodingFilter -> org.springframework.boot.servlet.filter.OrderedCharacterEncodingFilter
total beans: 146Bean của Boot nằm đó, và nó là OrderedCharacterEncodingFilter — subclass riêng của Boot có kèm thứ tự filter. Báo cáo --debug liệt kê nó dưới Positive matches:
HttpEncodingAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.filter.CharacterEncodingFilter' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)
- @ConditionalOnBooleanProperty (spring.servlet.encoding.enabled=true) matched (OnPropertyCondition)
HttpEncodingAutoConfiguration#characterEncodingFilter matched:
- @ConditionalOnMissingBean (types: org.springframework.web.filter.CharacterEncodingFilter; SearchStrategy: all) did not find any beans (OnBeanCondition)Giờ thêm một configuration class của tôi và không đổi gì khác:
@Configuration
public class AppConfig {
@Bean
CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding(StandardCharsets.UTF_8.name());
filter.setForceResponseEncoding(true);
return filter;
}
}Cũng lần chạy đó giờ báo:
CharacterEncodingFilter beans: [characterEncodingFilter]
characterEncodingFilter -> org.springframework.web.filter.CharacterEncodingFilter
total beans: 147Vẫn một bean filter, vẫn đúng tên bean đó, nhưng là của tôi. Và mục trong báo cáo đã chuyển từ Positive matches sang Negative matches:
HttpEncodingAutoConfiguration#characterEncodingFilter:
Did not match:
- @ConditionalOnMissingBean (types: org.springframework.web.filter.CharacterEncodingFilter; SearchStrategy: all) found beans of type 'org.springframework.web.filter.CharacterEncodingFilter' characterEncodingFilter (OnBeanCondition)
Con số tổng cũng đổi theo: 52 positive và 39 negative trở thành 51 positive và 40 negative. Đúng một quyết định lật, và bản thân HttpEncodingAutoConfiguration vẫn là positive match — chỉ riêng @Bean method của nó lùi lại. Mức chi tiết này rất quan trọng: một auto-configuration có nhiều bean vẫn tiếp tục đóng góp những bean bạn không thay thế.
⚠️ Back-off xét theo type (hoặc theo tên bean, khi annotation chỉ định tên), không phải chỉ theo tên bean, và nó chỉ hiệu quả nếu bean của bạn được đăng ký trước khi auto-configuration được đánh giá — điều này tự động đúng với mọi thứ nằm trong component scan hoặc
@Configurationclass của bạn. Một bean đăng ký từBeanFactoryPostProcessorsai thời điểm có thể lỡ mất cửa sổ đó.
Classpath quyết định thứ gì được cấu hình
Cách minh họa rõ nhất cho toàn bộ cơ chế chỉ tốn một dòng trong build.gradle. Vẫn application đó — không đổi một ký tự nào trong src/ — và thêm một dependency:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}Sáu jar mới xuất hiện: spring-boot-starter-thymeleaf, spring-boot-thymeleaf, thymeleaf, thymeleaf-spring6, attoparser và unbescape. Một trong số đó, spring-boot-thymeleaf-4.1.1.jar, mang file .imports chỉ có đúng một dòng:
org.springframework.boot.thymeleaf.autoconfigure.ThymeleafAutoConfigurationĐó là thay đổi duy nhất đối với danh sách candidate — 30 thành 31 — và đây là hệ quả:
spring-boot-starter-webmvc | + spring-boot-starter-thymeleaf | |
|---|---|---|
| Số jar trên runtime classpath | 39 | 45 |
| Số auto-configuration candidate | 30 | 31 |
| Positive matches | 52 | 58 |
| Negative matches | 39 | 45 |
| Số bean definition trong context | 146 | 155 |
| Bean mới có thể inject | — | defaultTemplateResolver, templateEngine, thymeleafViewResolver |

Một candidate sinh ra sáu mục positive mới và sáu mục negative mới, vì ThymeleafAutoConfiguration là class chứa các configuration class lồng bên trong, mỗi cái lại có condition riêng:
ThymeleafAutoConfiguration matched:
- @ConditionalOnClass found required classes 'org.thymeleaf.templatemode.TemplateMode', 'org.thymeleaf.spring6.SpringTemplateEngine' (OnClassCondition)
ThymeleafAutoConfiguration.DefaultTemplateResolverConfiguration matched:
- @ConditionalOnMissingBean (names: defaultTemplateResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)
ThymeleafAutoConfiguration.ThymeleafWebMvcConfiguration matched:
- found 'session' scope (OnWebApplicationCondition)Còn nửa negative của cùng class đó cho thấy Thymeleaf đã có thể cấu hình thêm gì nếu classpath đầy đủ hơn:
ThymeleafAutoConfiguration.ThymeleafSecurityDialectConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect', 'org.springframework.security.web.server.csrf.CsrfToken' (OnClassCondition)
ThymeleafAutoConfiguration.ThymeleafWebFluxConfiguration:
Did not match:
- did not find reactive web application classes (OnWebApplicationCondition)Thêm Spring Security thì cái đầu tiên lật thành positive match mà bạn không viết dòng nào. Đó là toàn bộ mô hình: classpath chính là configuration.
Đọc CONDITIONS EVALUATION REPORT
Khởi động application với --debug trên command line (hoặc debug=true trong application.properties) và Boot in báo cáo ngay khi context sẵn sàng:
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar --server.port=8090 --debug============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
AopAutoConfiguration matched:
- @ConditionalOnBooleanProperty (spring.aop.auto=true) matched (OnPropertyCondition)
AopAutoConfiguration.ClassProxyingConfiguration matched:
- @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
- @ConditionalOnBooleanProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)Nó có bốn phần, luôn theo thứ tự này. Với project dependencies=web trần, báo cáo dài hơn 360 dòng một chút và phân bổ như sau:
| Phần | Nội dung | Số lượng ở đây |
|---|---|---|
| Positive matches | mọi class, class lồng và @Bean method có toàn bộ condition thỏa | 52 |
| Negative matches | mọi cái bị loại, kèm condition đã loại nó | 39 |
| Exclusions | các class bạn tự loại bằng exclude hoặc spring.autoconfigure.exclude | 0 |
| Unconditional classes | auto-configuration không có condition nào nên luôn áp dụng | 6 |
Ba chi tiết khiến báo cáo trở nên dễ đọc thay vì đáng sợ.
Một mục không đồng nghĩa với một candidate. Ba mươi candidate tạo ra 91 mục, vì mỗi @Configuration class lồng có dòng riêng và mỗi @Bean method có condition cũng vậy. Dấu # trong tên nghĩa là một bean method: HttpEncodingAutoConfiguration#characterEncodingFilter là method, còn HttpEncodingAutoConfiguration là class.
Mỗi dòng đều nêu tên condition đã quyết định, đặt trong ngoặc ở cuối — (OnClassCondition), (OnBeanCondition), (OnPropertyCondition). Đó là tên class implementation, nên khi một dòng khiến bạn khó hiểu thì source chỉ cách một lần tìm kiếm.
Một mục negative vẫn có thể liệt kê phần đã khớp. Khi có nhiều condition và một cái thất bại, báo cáo hiện cả hai nửa. Chạy cùng project đó với --spring.servlet.encoding.enabled=false:
HttpEncodingAutoConfiguration:
Did not match:
- @ConditionalOnBooleanProperty (spring.servlet.encoding.enabled=true) found different value in property 'spring.servlet.encoding.enabled' (OnPropertyCondition)
Matched:
- @ConditionalOnClass found required class 'org.springframework.web.filter.CharacterEncodingFilter' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)Hai condition thỏa, một cái trượt, và cả class bị bỏ — kèm luôn @Bean method của nó, vốn không được đánh giá một khi class đã bị loại. Số liệu của lần chạy đó: 50 positive, 40 negative, và CharacterEncodingFilter beans: [] trong registry.
Hai phần cuối ngắn nhưng đáng biết. Exclusions rỗng trừ khi bạn tự loại thứ gì đó, nên đây là cách nhanh nhất để xác nhận một exclusion đã thật sự có hiệu lực. Unconditional classes là số ít class không có condition nào nên luôn áp dụng; ở project này chúng là:
Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfigurationTìm ra vì sao một bean có mặt hoặc vắng mặt
Đây là phần mà phần lớn developer không bao giờ học, và nó biến "Spring Boot không cấu hình cái tôi cần" từ trò đoán mò thành một lần tra cứu hai phút.
- Chạy với
--debugvà ghi ra file. Báo cáo dài hàng trăm dòng và bạn cần tìm kiếm trong đó, không phải cuộn tay. - Tìm auto-configuration theo tên. Nếu chưa biết tên, tìm theo type của bean —
grep -i thymeleaf,grep -i datasource. Các mục trong báo cáo đều nêu tên class mà chúng canh giữ. - Xem kết quả nằm ở phần nào. Nếu nằm dưới Positive matches thì configuration đã chạy và vấn đề nằm chỗ khác — một giá trị property, một bean bạn đã override, một URL mapping. Nếu nằm dưới Negative matches thì đọc dòng
Did not match:; nó nói chính xác cái gì đang thiếu. - Nếu tên đó không xuất hiện ở đâu cả, class này không phải candidate trên classpath hiện tại. Dependency mang nó đang thiếu nên không file
.importsnào nêu tên nó. Đó là vấn đề của file build, không phải vấn đề cấu hình. - Kiểm tra Exclusions khi một configuration bạn mong đợi lại vắng mặt mà classpath trông vẫn đúng.
Bốn kiểu thất bại ứng với bốn cách sửa: @ConditionalOnClass trượt nghĩa là thêm dependency; @ConditionalOnMissingBean trượt nghĩa là bạn (hoặc một thư viện khác) đã khai báo bean đó rồi; @ConditionalOnProperty trượt nghĩa là set property; và một cái tên không xuất hiện nghĩa là jar không có ở đó.
Còn một dòng nữa cần nhận ra. Khi startup hỏng trước lúc context sẵn sàng, Boot không in được báo cáo và nói thẳng ra:
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.Dòng đó nghĩa là báo cáo vẫn tồn tại và bạn chỉ cách nó đúng một flag.
Thứ tự áp dụng, và vì sao bean của bạn được biết trước
Các auto-configuration đôi khi phụ thuộc lẫn nhau, nên @AutoConfiguration mang theo thuộc tính sắp thứ tự. Nó được meta-annotate bằng @Configuration(proxyBeanMethods = false), @AutoConfigureBefore và @AutoConfigureAfter, rồi alias chúng thành before, beforeName, after và afterName. WebMvcAutoConfiguration dùng cả hai dạng:
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class },
afterName = "org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration")
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public final class WebMvcAutoConfiguration {afterName nhận chuỗi vì class đó nằm trong một module có thể không có trên classpath — dùng class literal sẽ lỗi khi load. Thứ tự này cuối cùng nằm trong file metadata sinh tự động, cạnh các condition:
org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration.AutoConfigureAfter=org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration
org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638Nhưng thứ tự quan trọng nhất không phải giữa các auto-configuration với nhau — mà là giữa chúng và bạn. AutoConfigurationImportSelector là một DeferredImportSelector, và Spring xử lý deferred import sau khi mọi @Configuration class khác đã được parse. Component, @Bean method và @Import của bạn đều đã được biết trước khi condition đầu tiên của auto-configuration được đánh giá.
Đúng một sự thật đó biến @ConditionalOnMissingBean thành một lời hứa thay vì một cuộc đua. Boot nói thật khi bảo "chỉ khi bạn chưa làm", vì tới lúc nó hỏi thì câu trả lời đã an bài.
Tắt auto-configuration
Đôi khi bạn muốn loại hẳn một auto-configuration chứ không phải override nó. Có hai cách viết cho cùng một thao tác.
Trên annotation, nếu class có trên compile classpath:
@SpringBootApplication(exclude = HttpEncodingAutoConfiguration.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}Hoặc bằng property, nhận tên đầy đủ nên dùng được cho mọi trường hợp:
spring.autoconfigure.exclude=org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfigurationCả hai cho cùng kết quả, và báo cáo chứng minh điều đó — phần Exclusions không còn rỗng:
Exclusions:
-----------
org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfigurationVới exclusion đó, lần chạy báo 50 positive matches và CharacterEncodingFilter beans: []. @SpringBootApplication còn nhận excludeName cho dạng chuỗi.
Có hai kiểu hỏng đáng gặp chủ động. Loại một class không phải auto-configuration sẽ bị từ chối thẳng chứ không bị làm ngơ:
java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
- com.example.demo.ConditionsDemoCòn loại bỏ thứ mà một starter thật sự cần thì hạ gục application ngay lúc khởi động. Bỏ auto-configuration của Tomcat servlet khỏi một web application:
***************************
APPLICATION FAILED TO START
***************************
Description:
Web application could not be started as there was no org.springframework.boot.web.server.servlet.ServletWebServerFactory bean defined in the context.
Action:
Check your application's dependencies for a supported servlet web server.
Check the configured web application type.Cũng thông báo đó xuất hiện nếu bạn tắt toàn bộ cơ chế bằng spring.boot.enableautoconfiguration=false, tức phương án hủy diệt và gần như không bao giờ là thứ bạn muốn. Exclude là con dao mổ; dùng nó khi auto-configuration của một thư viện thật sự sai với hoàn cảnh của bạn, còn khi chỉ muốn hành vi khác đi thì cứ khai báo bean của mình.
FAQ
Danh sách auto-configuration trong Spring Boot 4 nằm ở đâu?
Trong META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, mỗi dòng một class, và mỗi jar có đóng góp auto-configuration đều có một file như vậy. META-INF/spring.factories với key EnableAutoConfiguration là cơ chế của Boot 2; nó bị deprecated ở 2.7, gỡ bỏ ở 3.0, và không jar nào trên classpath Boot 4.1.1 còn dùng nó cho mục đích này.
Vì sao auto-configuration của tôi không được áp dụng?
Chạy với --debug và tìm class đó trong CONDITIONS EVALUATION REPORT. Dưới Negative matches, dòng Did not match: nêu tên condition đã trượt, gần như luôn là thiếu class (thêm dependency), một bean bạn đã khai báo (đó là back-off đang làm đúng việc), hoặc một property chưa set hay set sai giá trị. Nếu class không xuất hiện trong báo cáo, jar mang nó không có trên classpath.
Chạy với --debug trên production có an toàn không?
Bản thân báo cáo vô hại — nó in một lần lúc khởi động và sau đó không tốn gì. Vấn đề là --debug còn nâng một nhóm logger cốt lõi lên mức DEBUG, khiến application ồn hơn hẳn lúc chạy. Để điều tra một lần trên service đang chạy thì thường chấp nhận được; để bật vĩnh viễn thì không. Nếu chỉ cần báo cáo, debug=true cũng hành xử y hệt, nên hãy tính trước việc tắt lại.
Khác nhau giữa @Conditional và @ConditionalOn* là gì?
@Conditional là annotation của Spring Framework, nhận một Condition implementation do bạn cung cấp. Họ @ConditionalOn* là của Spring Boot: 21 annotation, mỗi cái là một @Conditional với condition viết sẵn cho một câu hỏi phổ biến. Dùng annotation của Boot khi có cái vừa vặn, và tự implement Condition khi không cái nào hợp.
Tôi có dùng được @ConditionalOnMissingBean trong @Configuration class của mình không?
Dùng được, nhưng kết quả phụ thuộc thứ tự parse nên không đáng tin. Auto-configuration được import sau cùng, đó là lý do condition về sự tồn tại bean hoạt động tốt ở đó. Trong configuration của bạn, hãy ưu tiên @ConditionalOnClass, @ConditionalOnProperty hoặc một Condition tự viết, vì chúng trả lời những câu hỏi không phụ thuộc vào thời điểm class được parse.
Làm sao tắt một auto-configuration mà không đụng tới classpath?
@SpringBootApplication(exclude = TheAutoConfiguration.class), hoặc spring.autoconfigure.exclude với tên đầy đủ. Kiểm tra phần Exclusions của báo cáo --debug để xác nhận nó có hiệu lực, và nhớ rằng loại bỏ một configuration mà cái khác phụ thuộc vào có thể khiến application không khởi động được.
Kết luận
Auto-configuration chỉ là bean, condition và registry — đúng những gì cả chương này nói tới. @EnableAutoConfiguration import một selector; selector đó hợp các file .imports từ mọi jar trên classpath thành danh sách candidate — 30 cái với một web app Boot 4.1.1 tối giản, rải trên sáu jar chứ không gom vào một; một bộ lọc metadata rẻ tiền loại ngay những cái hiển nhiên không áp dụng được; và những cái sống sót là các @Configuration class bình thường, với các annotation @ConditionalOn* lần lượt quyết định @Bean method nào được chạy. @ConditionalOnMissingBean là mảnh ghép khiến mọi thứ dễ sống chung: Boot cấu hình phần bạn chưa làm, và khai báo bean chính là cách override. Khi có chỗ nào khiến bạn bất ngờ, --debug in ra toàn bộ nhật ký quyết định kèm lý do cho từng dòng.
Vậy là khép lại Chương 1. Bạn đã biết container là gì, bean vào đó bằng cách nào, chúng được nối với nhau ra sao, sống bao lâu, và những bean bạn chưa từng viết đến từ đâu. Chương tiếp theo nói về việc chỉ dẫn cho application lúc chạy, và nó bắt đầu bằng hai file mà mọi project Spring Boot đều có: application.properties và application.yml — cú pháp của chúng, khác nhau chỗ nào, và đọc giá trị ra bằng @Value.