1. 引言
Spring
Boot最为人称道的特性就是”约定优于配置”——只需引入一个starter依赖,相关的Bean就会被自动注册到Spring容器中,无需手动编写XML配置或大量的@Bean定义。那么这背后的魔法是如何实现的?
本文将从@SpringBootApplication注解出发,逐层拆解Spring
Boot自动配置的完整链路,最后通过开发一个自定义Starter来将理论付诸实践。
2. @SpringBootApplication注解拆解
一切从主类上的@SpringBootApplication开始:
1 2 3 4 5 6 @SpringBootApplication public class MyApplication { public static void main (String[] args) { SpringApplication.run(MyApplication.class, args); } }
@SpringBootApplication是一个组合注解,包含三个核心注解:
graph TB
SBA["@SpringBootApplication"] --> SC["@SpringBootConfiguration<br/>(本质是@Configuration)"]
SBA --> EAC["@EnableAutoConfiguration<br/>自动配置的核心入口"]
SBA --> CS["@ComponentScan<br/>包扫描"]
EAC --> AI["@AutoConfigurationPackage<br/>注册主类所在包"]
EAC --> ISR["@Import(AutoConfigurationImportSelector.class)<br/>加载自动配置类"]
ISR --> SF["读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"]
style SBA fill:#FF6347,color:#fff
style EAC fill:#FFD700,stroke:#333,stroke-width:2px
style ISR fill:#90EE90
style SF fill:#87CEEB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude") Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName") String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; }
3. @EnableAutoConfiguration的工作原理
3.1
AutoConfigurationImportSelector
这是自动配置的核心引擎,它实现了DeferredImportSelector接口,负责筛选和加载自动配置类:
sequenceDiagram
participant Spring as Spring容器启动
participant Selector as AutoConfigurationImportSelector
participant Loader as 配置文件加载器
participant Filter as 条件过滤器
Spring->>Selector: process()
Selector->>Loader: 读取AutoConfiguration.imports
Loader-->>Selector: 返回所有候选配置类(100+个)
Selector->>Selector: 去重
Selector->>Selector: 应用exclude排除
Selector->>Filter: 应用@Conditional条件过滤
Filter-->>Selector: 返回满足条件的配置类
Selector-->>Spring: 返回最终要导入的配置类(通常20-30个)
Spring->>Spring: 注册Bean定义
核心加载流程代码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class AutoConfigurationImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return autoConfigurationEntry.getConfigurations().toArray(new String [0 ]); } protected AutoConfigurationEntry getAutoConfigurationEntry ( AnnotationMetadata annotationMetadata) { List<String> configurations = getCandidateConfigurations( annotationMetadata, null ); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, null ); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry (configurations, exclusions); } }
3.2 配置文件的演变
Spring Boot加载自动配置类的配置文件经历了一次重要变化:
graph LR
subgraph "Spring Boot 2.x"
SF2["META-INF/spring.factories"]
SF2 --> SF2C["org.springframework.boot.autoconfigure.EnableAutoConfiguration=\<br/>com.example.FooAutoConfiguration,\<br/>com.example.BarAutoConfiguration"]
end
subgraph "Spring Boot 3.x"
SF3["META-INF/spring/<br/>org.springframework.boot.autoconfigure.AutoConfiguration.imports"]
SF3 --> SF3C["com.example.FooAutoConfiguration<br/>com.example.BarAutoConfiguration<br/>(每行一个类名)"]
end
SF2 -->|"Spring Boot 2.7开始<br/>并行支持,3.0移除旧方式"| SF3
style SF2 fill:#FFD700
style SF3 fill:#90EE90
Spring Boot
2.x :使用META-INF/spring.factories文件,以key-value格式配置
1 2 3 4 org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ com.example.autoconfigure.FooAutoConfiguration,\ com.example.autoconfigure.BarAutoConfiguration
Spring Boot
3.x :使用新的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
1 2 3 # META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports com.example.autoconfigure.FooAutoConfiguration com.example.autoconfigure.BarAutoConfiguration
4. @Conditional条件注解体系
自动配置的关键在于”按需加载”——只有满足特定条件时才生效。Spring
Boot提供了丰富的@Conditional注解:
graph TB
C["@Conditional<br/>(Spring Framework)"] --> CB["@ConditionalOnBean<br/>容器中存在指定Bean"]
C --> CMB["@ConditionalOnMissingBean<br/>容器中不存在指定Bean"]
C --> CC["@ConditionalOnClass<br/>classpath中存在指定类"]
C --> CMC["@ConditionalOnMissingClass<br/>classpath中不存在指定类"]
C --> CP["@ConditionalOnProperty<br/>配置属性满足条件"]
C --> CWA["@ConditionalOnWebApplication<br/>是Web应用"]
C --> CR["@ConditionalOnResource<br/>存在指定资源"]
C --> CE["@ConditionalOnExpression<br/>SpEL表达式为true"]
style C fill:#FFD700,stroke:#333,stroke-width:2px
以DataSourceAutoConfiguration为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @AutoConfiguration(before = SqlInitializationAutoConfiguration.class) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") @EnableConfigurationProperties(DataSourceProperties.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({ DataSourceConfiguration.Hikari.class, // HikariCP数据源 DataSourceConfiguration.Tomcat.class, // Tomcat JDBC数据源 DataSourceConfiguration.Dbcp2.class, // DBCP2数据源 }) protected static class PooledDataSourceConfiguration { } }
4.1 @ConditionalOnMissingBean的重要性
这是自动配置中最常用的条件注解,它确保了”用户配置优先”的原则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration public class MyServiceAutoConfiguration { @Bean @ConditionalOnMissingBean public MyService myService () { return new DefaultMyService (); } }@Configuration public class UserConfig { @Bean public MyService myService () { return new CustomMyService (); } }
5. 配置加载顺序
Spring Boot有严格的配置加载优先级,优先级高的会覆盖优先级低的:
graph TB
P1["1. 命令行参数<br/>--server.port=8080"] --> P2
P2["2. Java系统属性<br/>System.getProperties()"] --> P3
P3["3. OS环境变量"] --> P4
P4["4. application-{profile}.properties<br/>特定Profile配置"] --> P5
P5["5. application.properties<br/>默认配置"] --> P6
P6["6. @PropertySource注解"] --> P7
P7["7. SpringApplication.setDefaultProperties()<br/>默认属性"]
style P1 fill:#FF6347,color:#fff
style P7 fill:#90EE90
P1 ~~~ NOTE["优先级从高到低<br/>高优先级覆盖低优先级"]
配置文件的搜索路径(优先级从高到低):
1 2 3 4 5 1. file:./config/ # 项目根目录/config/ 2. file:./config/*/ # 项目根目录/config/子目录/ 3. file:./ # 项目根目录 4. classpath:/config/ # classpath下的config/ 5. classpath:/ # classpath根目录
5.1 自动配置类的加载顺序控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @AutoConfiguration( after = DataSourceAutoConfiguration.class, // 在数据源配置之后 before = FlywayAutoConfiguration.class // 在Flyway配置之前 ) public class MyAutoConfiguration { }@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureBefore(FlywayAutoConfiguration.class)
6. 自定义Starter开发实战
6.1 项目结构
开发一个自定义Starter,通常由两个模块组成:
graph TB
subgraph "my-spring-boot-starter"
STARTER["my-service-spring-boot-starter<br/>(starter模块)"]
AC["my-service-spring-boot-autoconfigure<br/>(autoconfigure模块)"]
STARTER -->|依赖| AC
end
STARTER --> NOTE1["只包含pom.xml<br/>聚合依赖"]
AC --> NOTE2["包含自动配置类<br/>@Conditional逻辑<br/>配置属性绑定"]
style STARTER fill:#87CEEB
style AC fill:#FFD700
命名规范: -
官方Starter:spring-boot-starter-{name}(如spring-boot-starter-web)
-
第三方Starter:{name}-spring-boot-starter(如mybatis-spring-boot-starter)
6.2 完整实现
下面实现一个短信发送服务的自定义Starter。
Step 1: 配置属性类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.example.sms.autoconfigure;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "sms") public class SmsProperties { private boolean enabled = true ; private String provider = "aliyun" ; private String accessKeyId; private String accessKeySecret; private String signName; private int timeoutMs = 5000 ; private int maxRetries = 3 ; public boolean isEnabled () { return enabled; } public void setEnabled (boolean enabled) { this .enabled = enabled; } public String getProvider () { return provider; } public void setProvider (String provider) { this .provider = provider; } public String getAccessKeyId () { return accessKeyId; } public void setAccessKeyId (String accessKeyId) { this .accessKeyId = accessKeyId; } public String getAccessKeySecret () { return accessKeySecret; } public void setAccessKeySecret (String accessKeySecret) { this .accessKeySecret = accessKeySecret; } public String getSignName () { return signName; } public void setSignName (String signName) { this .signName = signName; } public int getTimeoutMs () { return timeoutMs; } public void setTimeoutMs (int timeoutMs) { this .timeoutMs = timeoutMs; } public int getMaxRetries () { return maxRetries; } public void setMaxRetries (int maxRetries) { this .maxRetries = maxRetries; } }
Step 2: 服务接口和实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.example.sms;public interface SmsService { boolean send (String phoneNumber, String templateCode, java.util.Map<String, String> params) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.example.sms;import java.util.Map;public class AliyunSmsService implements SmsService { private final String accessKeyId; private final String accessKeySecret; private final String signName; private final int timeoutMs; private final int maxRetries; public AliyunSmsService (String accessKeyId, String accessKeySecret, String signName, int timeoutMs, int maxRetries) { this .accessKeyId = accessKeyId; this .accessKeySecret = accessKeySecret; this .signName = signName; this .timeoutMs = timeoutMs; this .maxRetries = maxRetries; } @Override public boolean send (String phoneNumber, String templateCode, Map<String, String> params) { System.out.printf("Aliyun SMS: sending to %s, template=%s, sign=%s, params=%s%n" , phoneNumber, templateCode, signName, params); return true ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.sms;import java.util.Map;public class TencentSmsService implements SmsService { private final String accessKeyId; private final String accessKeySecret; private final String signName; public TencentSmsService (String accessKeyId, String accessKeySecret, String signName) { this .accessKeyId = accessKeyId; this .accessKeySecret = accessKeySecret; this .signName = signName; } @Override public boolean send (String phoneNumber, String templateCode, Map<String, String> params) { System.out.printf("Tencent SMS: sending to %s, template=%s, sign=%s, params=%s%n" , phoneNumber, templateCode, signName, params); return true ; } }
Step 3: 自动配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.example.sms.autoconfigure;import com.example.sms.AliyunSmsService;import com.example.sms.SmsService;import com.example.sms.TencentSmsService;import org.springframework.boot.autoconfigure.AutoConfiguration;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@AutoConfiguration @ConditionalOnClass(SmsService.class) @ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(SmsProperties.class) public class SmsAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "sms", name = "provider", havingValue = "aliyun", matchIfMissing = true) static class AliyunSmsConfiguration { @Bean @ConditionalOnMissingBean(SmsService.class) public SmsService aliyunSmsService (SmsProperties properties) { return new AliyunSmsService ( properties.getAccessKeyId(), properties.getAccessKeySecret(), properties.getSignName(), properties.getTimeoutMs(), properties.getMaxRetries() ); } } @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "sms", name = "provider", havingValue = "tencent") static class TencentSmsConfiguration { @Bean @ConditionalOnMissingBean(SmsService.class) public SmsService tencentSmsService (SmsProperties properties) { return new TencentSmsService ( properties.getAccessKeyId(), properties.getAccessKeySecret(), properties.getSignName() ); } } }
Step 4: 注册自动配置
1 2 3 # 文件: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports # (Spring Boot 3.x) com.example.sms.autoconfigure.SmsAutoConfiguration
1 2 3 4 org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ com.example.sms.autoconfigure.SmsAutoConfiguration
Step 5: 使用方配置
1 2 3 4 5 6 7 8 9 sms: enabled: true provider: aliyun access-key-id: ${SMS_ACCESS_KEY_ID} access-key-secret: ${SMS_ACCESS_KEY_SECRET} sign-name: MyApp timeout-ms: 3000 max-retries: 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController @RequestMapping("/api/sms") public class SmsController { private final SmsService smsService; public SmsController (SmsService smsService) { this .smsService = smsService; } @PostMapping("/send") public ResponseEntity<String> send (@RequestParam String phone, @RequestParam String templateCode) { boolean success = smsService.send(phone, templateCode, java.util.Map.of("code" , "123456" )); return success ? ResponseEntity.ok("sent" ) : ResponseEntity.status(500 ).body("failed" ); } }
7. 调试自动配置
7.1 查看自动配置报告
1 2 3 4 5 6 java -jar app.jar --debug debug=true
启动日志中会输出详细的自动配置报告:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ============================ CONDITIONS EVALUATION REPORT ============================ Positive matches: ----------------- DataSourceAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' Negative matches: ----------------- MongoAutoConfiguration: Did not match: - @ConditionalOnClass did not find required class 'com.mongodb.client.MongoClient'
7.2 排除特定自动配置
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class }) public class MyApplication { }
8. 最佳实践
自定义Bean优先于自动配置 :始终使用@ConditionalOnMissingBean确保用户自定义配置能覆盖默认配置。
配置属性要有合理的默认值 :让用户在最少配置的情况下就能使用。
使用@AutoConfiguration替代@Configuration :Spring
Boot 3.x推荐使用专门的注解来标识自动配置类。
不要在自动配置类中使用@ComponentScan :避免意外扫描到用户的Bean。
提供配置元数据 :创建META-INF/additional-spring-configuration-metadata.json,让IDE能提供配置提示。
合理使用@Conditional组合 :精确控制配置生效条件,避免不必要的Bean创建。
注意自动配置的顺序 :使用@AutoConfiguration(after/before)明确依赖关系。
9. 总结
Spring Boot自动配置的核心链路可以概括为:
@SpringBootApplication ->
@EnableAutoConfiguration ->
AutoConfigurationImportSelector
AutoConfigurationImportSelector从AutoConfiguration.imports文件中加载所有候选配置类
通过@Conditional系列注解进行条件过滤,只有满足条件的配置类才会生效
生效的配置类中定义的@Bean方法被执行,Bean被注册到Spring容器
@ConditionalOnMissingBean确保了用户自定义配置始终优先于自动配置
理解这个链路后,不仅能更好地使用Spring
Boot提供的自动配置,还能开发高质量的自定义Starter,将通用功能封装为可复用的组件,提升团队的开发效率。