跳到主要内容
Spring Boot 自动配置与 @EnableAutoConfiguration 原理 | 极客日志
Java java
Spring Boot 自动配置与 @EnableAutoConfiguration 原理 Spring Boot 自动配置的核心不是魔法,而是 `@EnableAutoConfiguration`、`spring.factories` 和一组条件注解协作完成的装配流程。文章先对比了传统 Spring 的繁琐 XML 配置,再拆解 `@SpringBootApplication` 的组成、自动配置类的加载来源、条件注解的判断逻辑,以及自动配置的排序和覆盖规则。后半部分补充了自定义 starter 的写法、常见冲突与排查方式、启动性能优化手段,并说明了 Spring Boot 3.x 在自动配置和原生镜像支持上的变化。
1. 自动配置到底做了什么
Spring Boot 最省心的地方,不是'零配置'这几个字,而是它把一堆本来要你手工接上的东西,按条件提前准备好了。传统 Spring 时代,XML 往往又长又散,组件扫描、MVC、事务、数据源、视图解析器都得自己串起来;换成 Spring Boot,入口缩成一个 @SpringBootApplication,启动类看起来轻了,背后却多了不少判断逻辑。
<beans xmlns ="http://www.springframework.org/schema/beans" xmlns:context ="http://www.springframework.org/schema/context" xmlns:mvc ="http://www.springframework.org/schema/mvc" xmlns:tx ="http://www.springframework.org/schema/tx" >
<context:component-scan base-package ="com.example" />
<mvc:annotation-driven />
<bean >
<property name ="url" value ="${jdbc.url}" />
<property name ="username" value ="${jdbc.username}" />
<property name ="password" value ="${jdbc.password}" />
</bean >
<bean >
< = = />
property
name
"dataSource"
ref
"dataSource"
</bean >
<tx:annotation-driven transaction-manager ="transactionManager" />
<bean >
<property name ="prefix" value ="/WEB-INF/views/" />
<property name ="suffix" value =".jsp" />
</bean >
</beans >
代码清单 1:传统 Spring 的 XML 配置
@SpringBootApplication
public class Application {
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
}
真正让人好奇的是:为什么看起来什么都没配,数据源、事务、MVC 却还是能起来?答案不在'魔法',而在一套条件装配机制。
2. @EnableAutoConfiguration 是入口 @SpringBootApplication 不是一个单独的新东西,它本质上是组合注解。源码里最关键的部分就是 @EnableAutoConfiguration,它负责把自动配置这条链路接起来。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
}
代码清单 3:@SpringBootApplication 的核心组成
它背后的判断思路并不复杂:ClassPath 里有什么,就先按你大概率会用到什么去装配。比如有 DataSource,就考虑数据源;有 DispatcherServlet,就考虑 Web MVC。条件不满足,相关配置就直接跳过。
3. 自动配置类从哪里来 Spring Boot 不会凭空'猜'出配置类,它靠的是 META-INF/spring.factories。这个文件就是自动配置的索引表,告诉框架:有哪些类应该在启动时被拿出来评估。
spring-boot-autoconfigure 里能看到类似内容:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,
# 后面还有 100 多个...
代码清单 4:spring.factories 示例
启动时,Spring Boot 会把所有 jar 包里的 META-INF/spring.factories 扫一遍,把对应的自动配置类收集起来。这个步骤看上去机械,实际很关键;你项目里多引一个 starter,背后可能就多一串自动配置候选项。
4. 条件注解决定谁生效 自动配置之所以'聪明',靠的是条件注解,而不是某种神秘推断。它们本质上就是一组 if 语句,只是判断点放在了容器刷新阶段。
注解 作用 常见场景 @ConditionalOnClassClassPath 中存在指定类时生效 Redis、MongoDB、Web MVC 自动配置 @ConditionalOnMissingClassClassPath 中不存在指定类时生效 排除某些默认配置 @ConditionalOnBean容器中存在指定 Bean 时生效 有 DataSource 时才配 JdbcTemplate @ConditionalOnMissingBean容器中不存在指定 Bean 时生效 用户没自定义时提供默认 Bean @ConditionalOnProperty配置文件中存在指定属性时生效 用配置开关控制功能 @ConditionalOnWebApplication是 Web 应用时生效 Web 相关 Bean @ConditionalOnNotWebApplication不是 Web 应用时生效 非 Web 场景 Bean
public interface Condition {
boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) ;
}
class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome (ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
ConditionalOnClass.class.getName(), true );
if (attributes != null ) {
List<String> classNames = (List<String>) attributes.get("value" );
for (String className : classNames) {
if (!ClassUtils.isPresent(className, context.getClassLoader())) {
return ConditionOutcome.noMatch("required class not found: " + className);
}
}
}
return ConditionOutcome.match();
}
}
这套机制的顺序也有讲究。先做便宜的检查,再做贵的检查,能省一点是一点。比如 @ConditionalOnProperty 只看配置项,开销通常比 ClassPath 扫描小得多;在自定义自动配置里,我一般会先把规则想清楚,再决定条件怎么叠,别把最重的判断摆在最前面。
5. spring.factories 和自定义自动配置 理解了自动配置的入口和条件判断后,自己写一个 starter 就不算难了。下面这个短信服务示例就是比较常见的写法:属性类负责接收配置,自动配置类负责按条件创建 Bean。
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
private String accessKey;
private String secretKey;
private String signName;
private String templateCode;
}
@Configuration
@EnableConfigurationProperties(SmsProperties.class)
@ConditionalOnClass(SmsClient.class)
@ConditionalOnProperty(prefix = "sms", value = "enabled", havingValue = "true", matchIfMissing = true)
public class SmsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SmsClient smsClient (SmsProperties properties) {
return new SmsClient (
properties.getAccessKey(),
properties.getSecretKey(),
properties.getSignName(),
properties.getTemplateCode()
);
}
@Bean
public SmsService smsService (SmsClient smsClient) {
return new SmsService (smsClient);
}
}
注册到 resources/META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sms.autoconfigure.SmsAutoConfiguration
sms:
enabled: true
access-key: your-access-key
secret-key: your-secret-key
sign-name: 公司签名
template-code: SMS_123456
@Service
public class UserService {
@Autowired
private SmsService smsService;
public void register (String phone) {
smsService.sendVerifyCode(phone);
}
}
这类 starter 真正值钱的地方,不是'写起来酷',而是把一套容易出错的初始化流程收拢了。业务侧少碰底层细节,出问题时也更容易定位。
6. 自动配置的排序和覆盖 自动配置不是一股脑全进容器,它有顺序。顺序错了,Bean 可能先被创建,后面的配置再想补救就晚了。
Spring Boot 提供了三个常用排序手段:@AutoConfigureOrder、@AutoConfigureBefore、@AutoConfigureAfter。看 DataSourceAutoConfiguration 的源码就很直观:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@AutoConfigureBefore({ DataSourcePoolMetricsAutoConfiguration.class, XADataSourceAutoConfiguration.class })
@AutoConfigureAfter({ DataSourceInitializationConfiguration.class })
@Import({ DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class })
static class PooledDataSourceConfiguration { }
}
代码清单 6:DataSourceAutoConfiguration 的排序配置
它的意思很朴素:先完成数据源初始化,再让监控之类的配置接上去。这个顺序要是倒过来,很多指标类功能就会拿不到目标 Bean。
覆盖规则也类似。用户自己写的 @Bean,优先级通常高于自动配置;配置属性则介于二者之间,更多是在默认值上做覆盖:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
hikari:
maximum-pool-size: 20
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource () {
return DataSourceBuilder.create().build();
}
}
调试时,如果想看哪些配置生效了,可以直接开 DEBUG:
logging:
level:
org.springframework.boot.autoconfigure: DEBUG
java -jar myapp.jar --debug
输出里会列出 Positive matches、Negative matches 和被排除的自动配置。这个报告在排障时很实用,尤其是你怀疑某个 starter 抢了别人的 Bean 的时候。
7. 启动性能和自动配置的关系 自动配置本身不一定慢,慢的是相关依赖越来越多后,ClassPath 扫描和条件评估开始堆时间。项目一大,启动时间往往先从这里冒出来。
总启动时间:8.2 秒
├── 扫描 ClassPath:3.5 秒 (42.7%)
├── 加载自动配置类:2.1 秒 (25.6%)
├── 创建 Bean:1.8 秒 (22.0%)
└── 其他:0.8 秒 (9.7%)
如果确认某些自动配置根本用不上,直接排除比事后补救更省事:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class, // 如果不是 Web 应用
WebMvcAutoConfiguration.class, // 如果没有 Web 界面
SecurityAutoConfiguration.class, // 如果不需要安全
MailSenderAutoConfiguration.class // 如果不需要邮件
})
public class Application {
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
}
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
Spring Boot 2.2 之后还能开懒加载,适合那种启动要求高、首屏不敏感的服务:
@SpringBootApplication
@Lazy
public class Application {
public static void main (String[] args) {
SpringApplication app = new SpringApplication (Application.class);
app.setLazyInitialization(true );
app.run(args);
}
}
这个办法能压启动时间,但第一次真正打到 Bean 时会更慢一点。它不是银弹,只是把成本往后挪。
8. 自定义 starter 更像工程活 在团队里做 starter,最容易踩的不是语法问题,而是边界问题。你要解决的是'大家都能复用',不是'我自己这台机器能跑'。所以配置默认值、开关、兼容性都得想在前面。
@ConfigurationProperties(prefix = "monitor")
public class MonitorProperties {
private boolean enabled = true ;
private String applicationName;
private String endpoint = "http://monitor.internal.company.com" ;
private int reportInterval = 30 ;
}
@Configuration
@EnableConfigurationProperties(MonitorProperties.class)
@ConditionalOnClass(MonitorClient.class)
@ConditionalOnProperty(prefix = "monitor", value = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
public class MonitorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MonitorClient monitorClient (MonitorProperties properties) {
return new MonitorClient (properties.getEndpoint(), properties.getApplicationName());
}
@Bean
public MonitorAspect monitorAspect (MonitorClient monitorClient) {
return new MonitorAspect (monitorClient);
}
@Bean
public MonitorEndpoint monitorEndpoint () {
return new MonitorEndpoint ();
}
}
@Endpoint(id = "monitor")
@Component
public class MonitorEndpoint {
@ReadOperation
public Map<String, Object> status () {
Map<String, Object> status = new HashMap <>();
status.put("status" , "UP" );
status.put("timestamp" , System.currentTimeMillis());
return status;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.company.starter.monitor.MonitorAutoConfiguration
org.springframework.boot.autoconfigure.EnableConfigurationProperties=\
com.company.starter.monitor.MonitorProperties
引用 starter 后,业务代码就不用关心监控客户端怎么初始化了。这个思路在企业里很常见,省掉的是重复劳动,也减少了配置漂移。
9. 常见问题,基本都绕不开这几个 自动配置冲突是最常见的。两个 starter 都想注册同名 Bean,结果就是容器报错。这个时候要么排除一个自动配置,要么给自定义 Bean 加 @Primary。
@SpringBootApplication(exclude = { RedisAutoConfiguration.class // 排除一个 })
public class Application {
}
@Configuration
public class RedisConfig {
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate <>();
template.setConnectionFactory(factory);
return template;
}
}
条件注解不生效时,也别先怀疑 Spring Boot,本质上通常是类根本不在运行时 ClassPath 里,或者被别的条件提前挡掉了。排查最实在的办法还是看条件评估报告,而不是凭感觉猜。
配置属性读不到,多半是前缀、名字、位置写错了。给属性类加校验和元数据,问题会少很多:
@ConfigurationProperties(prefix = "my.starter")
@Validated
public class MyProperties {
@NotEmpty(message = "name 不能为空")
private String name;
@Min(value = 1, message = "version 必须大于 0")
private int version;
private boolean enabled = true ;
}
# META-INF/spring-configuration-metadata.json
{
"properties" : [
{
"name" : "my.starter.name" ,
"type" : "java.lang.String" ,
"description" : "starter 名称" ,
"sourceType" : "com.example.MyProperties"
} ,
{
"name" : "my.starter.version" ,
"type" : "java.lang.Integer" ,
"description" : "版本号" ,
"defaultValue" : 1
}
]
}
10. Spring Boot 3.x 的变化 Spring Boot 3.0 基于 Spring Framework 6,自动配置这块做了不少整理。最直观的变化之一,是注解和配置模型更清晰了,配合 GraalVM 原生镜像时也少了很多历史包袱。
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnClass(DataSource.class)
@ConditionalOnClass(EmbeddedDatabaseType.class)
@AutoConfiguration
@Lazy
public class MyAutoConfiguration {
}
@NativeHint(types = @TypeHint(types = { DataSource.class, JdbcTemplate.class }), options = {"--enable-https"})
@AutoConfiguration
public class DataSourceAutoConfiguration {
}
如果只看结果,Spring Boot 3.0 在启动时间、内存占用和条件评估上都更轻一些。这个提升不是'突然飞起来',而是把很多老旧路径收紧了。
11. 我会怎么用自动配置 如果是日常业务项目,我的优先级很简单:先用默认 starter,确认没引入多余依赖;再看启动日志里有没有明显的冲突;最后才考虑自己写 starter 或排除自动配置。很多团队一上来就自定义,实际是把简单问题做复杂了。
定期看 mvn dependency:tree
对不需要的自动配置直接 exclude
公共 starter 保持小而稳,别把一堆业务逻辑塞进去
开发和排障阶段多看条件评估报告
自定义配置尽量加校验和默认值
12. 收个尾 Spring Boot 的自动配置不是'帮你想好了',只是帮你把常见路径先铺平。理解了 @EnableAutoConfiguration、spring.factories 和条件注解之后,它就不再神秘了。真正的分界线在这里:懂原理的人,会拿它提效;不懂的人,容易被依赖和条件判断绕晕。
如果要练手,我建议自己写一个最小的 starter。别一开始就做复杂集成,先做一个简单的 HelloAutoConfiguration,让它根据配置决定是否输出一段文本。这个过程很短,但能把自动配置的骨架看明白。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online