Spring Boot 自动装配源码:@EnableAutoConfiguration 的 SPI 加载全链路
标签:Spring Boot | 自动装配 | SPI | 源码分析
一、问题引入
在日常开发中,我们只需要在 pom.xml 中引入 spring-boot-starter-web,一个完整的 Web 项目就自动配置好了------内嵌 Tomcat、Spring MVC、Jackson 序列化全部就绪。这背后的"魔法"到底是什么?本文将从 @SpringBootApplication 注解切入,逐行追踪 spring.factories 的 SPI 加载全链路,彻底揭开 Spring Boot 自动装配的神秘面纱。
二、核心入口:@SpringBootApplication 的元注解拆解
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}
三个核心元注解:
@SpringBootConfiguration:本质是@Configuration,标识配置类@EnableAutoConfiguration:自动装配的总开关@ComponentScan:组件扫描,默认扫描当前包及子包
关键点 :@EnableAutoConfiguration 是自动装配的核心驱动力。
三、@EnableAutoConfiguration 的结构
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // ← 核心入口
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@Import(AutoConfigurationImportSelector.class) 是自动装配的触发点。Spring 在解析配置类时,会处理 @Import 导入的 ImportSelector 实现类。
四、AutoConfigurationImportSelector 的加载全链路
4.1 ImportSelector 接口的调用时机
java
public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware, ... {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry
= getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
调用链路:
ConfigurationClassParser.parse()
→ processDeferredImportSelectors()
→ DeferredImportSelector.selectImports()
→ AutoConfigurationImportSelector.getAutoConfigurationEntry()
4.2 getAutoConfigurationEntry() 的完整流程
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata metadata) {
if (!isEnabled(metadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(metadata);
// 第1步:从 spring.factories 加载所有自动配置类
List<String> configurations = getCandidateConfigurations(metadata, attributes);
// 第2步:去重
configurations = removeDuplicates(configurations);
// 第3步:读取 @EnableAutoConfiguration 的 exclude 排除项
Set<String> exclusions = getExclusions(metadata, attributes);
// 第4步:校验排除类是否合法
checkExcludedClasses(configurations, exclusions);
// 第5步:从候选配置中移除排除项
configurations.removeAll(exclusions);
// 第6步:通过 AutoConfigurationImportFilter 进行条件过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 第7步:发布自动装配导入事件(用于监控)
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
4.3 getCandidateConfigurations():SPI 加载的核心
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), // EnableAutoConfiguration.class
getBeanClassLoader()
)
);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories.");
return configurations;
}
SpringFactoriesLoader.loadFactoryNames() 是 SPI 加载的核心方法。
五、SpringFactoriesLoader 的 SPI 机制详解
5.1 文件定位与加载
java
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) return result;
result = new HashMap<>();
try {
// 加载所有 META-INF/spring.factories 文件
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, k -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories", ex);
}
cache.put(classLoader, result);
return result;
}
}
加载过程:
- 扫描 classpath 下所有 jar 包 中的
META-INF/spring.factories - 解析 Properties 格式的
key=value(逗号分隔多个值) - 按
factoryTypeName聚合,返回实现类全限定名列表 - 使用
ConcurrentReferenceHashMap缓存结果,避免重复加载
5.2 spring.factories 文件示例
properties
# spring-boot-autoconfigure-3.x.jar!/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...(约 150 个自动配置类)
六、条件过滤:@Conditional 家族的生效判定
加载候选配置后,需要判断哪些配置类真正应该生效 。Spring Boot 通过 @Conditional 系列注解实现:
| 注解 | 条件 | 示例场景 |
|---|---|---|
@ConditionalOnClass |
classpath 中存在指定类 | 存在 Servlet.class 才启用 Web 配置 |
@ConditionalOnMissingClass |
classpath 中不存在指定类 | 兼容旧版本库的判断 |
@ConditionalOnBean |
容器中存在指定 Bean | DataSource 存在才启用 MyBatis |
@ConditionalOnMissingBean |
容器中不存在指定 Bean | 用户未自定义时提供默认实现 |
@ConditionalOnProperty |
指定属性满足条件 | spring.datasource.url 配置后才启用 |
@ConditionalOnWebApplication |
当前是 Web 环境 | 判断是否存在 ServletContext |
6.1 DispatcherServletAutoConfiguration 的条件配置
java
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET) // 必须是 Servlet Web 环境
@ConditionalOnClass(DispatcherServlet.class) // 必须引入 spring-webmvc
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class,
name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfig) {
// 注册 DispatcherServlet 到 Servlet 容器
}
}
6.2 条件评估的底层:ConditionEvaluator
java
class ConditionEvaluator {
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false; // 没有 @Conditional,直接生效
}
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass);
conditions.add(condition);
}
}
for (Condition condition : conditions) {
if (matches(condition, metadata, phase) == false) {
return true; // 任一条件不匹配,跳过该配置类
}
}
return false;
}
}
七、Spring Boot 3.x 的新变化:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
从 Spring Boot 2.7 开始,废弃了 META-INF/spring.factories,改为新的导入文件:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
...
迁移原因:
spring.factories一个文件承载所有 SPI,过于臃肿- 新格式每个自动配置类独立一行,更清晰
- 支持新的
@AutoConfiguration注解(替代@Configuration)
兼容性 :Spring Boot 3.x 仍然兼容 spring.factories 格式,但推荐使用新格式。
八、自定义 Starter 的实战:手写一个自动配置模块
8.1 项目结构
my-spring-boot-starter/
├── pom.xml
└── src/main/
├── java/com/example/autoconfigure/
│ ├── MyAutoConfiguration.java
│ ├── MyProperties.java
│ └── MyService.java
└── resources/
└── META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
8.2 自动配置类
java
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnProperty(prefix = "my.starter", name = "enabled",
havingValue = "true", matchIfMissing = true)
public class MyAutoConfiguration {
private final MyProperties properties;
public MyAutoConfiguration(MyProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean // 用户未自定义时才创建
public MyService myService() {
MyService service = new MyService();
service.setName(properties.getName());
service.setTimeout(properties.getTimeout());
return service;
}
}
8.3 配置属性类
java
@ConfigurationProperties(prefix = "my.starter")
@Data
public class MyProperties {
private boolean enabled = true;
private String name = "default";
private int timeout = 5000;
}
8.4 注册文件
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfigure.MyAutoConfiguration
九、总结
| 阶段 | 核心操作 | 关键源码位置 |
|---|---|---|
| 触发 | 解析 @Import |
ConfigurationClassParser.processImports() |
| 加载 | SPI 读取 spring.factories |
SpringFactoriesLoader.loadSpringFactories() |
| 筛选 | 候选配置去重+排除 | getAutoConfigurationEntry() |
| 过滤 | @Conditional 条件判定 |
ConditionEvaluator.shouldSkip() |
| 注册 | Bean 定义注册到容器 | ConfigurationClassBeanDefinitionReader.loadBeanDefinitions() |
核心设计思想:
- 约定优于配置:通过 classpath 上的 jar 包自动发现功能
- 条件化装配 :
@Conditional精准控制配置的生效时机 - 用户定制优先 :
@ConditionalOnMissingBean保证用户自定义 Bean 覆盖默认配置 - SPI 解耦 :通过
spring.factories实现模块间的松耦合扩展