Spring Boot 自动装配源码


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;
    }
}

加载过程

  1. 扫描 classpath 下所有 jar 包 中的 META-INF/spring.factories
  2. 解析 Properties 格式的 key=value(逗号分隔多个值)
  3. factoryTypeName 聚合,返回实现类全限定名列表
  4. 使用 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()

核心设计思想

  1. 约定优于配置:通过 classpath 上的 jar 包自动发现功能
  2. 条件化装配@Conditional 精准控制配置的生效时机
  3. 用户定制优先@ConditionalOnMissingBean 保证用户自定义 Bean 覆盖默认配置
  4. SPI 解耦 :通过 spring.factories 实现模块间的松耦合扩展
相关推荐
Leaton Lee1 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐1 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs1 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐1 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司1 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
一条小锦吕*1 小时前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化
Jinkxs1 小时前
Prometheus - 监控微服务:Spring Boot 应用指标暴露与监控
spring boot·微服务·prometheus
码农阿豪1 小时前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端
追逐时光者1 小时前
一个基于 .NET 与 Avalonia 构建、面向 TrinityCore 的开源 WoW 数据库编辑器
后端·.net