乐于分享
好东西不私藏

Spring Boot 源码解析(三):自动配置源码深度剖析

Spring Boot 源码解析(三):自动配置源码深度剖析

大家好,我是老 J。

上期我们讲了 @SpringBootApplication 的组成,知道了自动配置的入口是 @EnableAutoConfiguration

这期深入到源码层面,剖析 自动配置的完整执行流程,看看 Spring Boot 到底是怎么把那些配置类加载进来的。

一、自动配置入口回顾

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)// 关键public@interface EnableAutoConfiguration {}

核心:@Import(AutoConfigurationImportSelector.class) 导入了 AutoConfigurationImportSelector

二、AutoConfigurationImportSelector 源码分析

1. 类继承体系

ImportSelector (接口)    │    └── DeferredImportSelector (接口,延迟导入)            │            └── AutoConfigurationImportSelector

DeferredImportSelector 会在所有 @Configuration 类解析完成后才执行,确保自动配置在用户配置之后处理(这样用户配置可以覆盖自动配置)。

2. selectImports 方法

这是最核心的方法,返回要导入的配置类名数组。

// AutoConfigurationImportSelector.java@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// 1. 检查是否开启自动配置(默认开启)if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;    }// 2. 加载自动配置元数据AutoConfigurationEntryautoConfigurationEntry=        getAutoConfigurationEntry(annotationMetadata);// 3. 返回配置类名数组return StringUtils.toStringArray(        autoConfigurationEntry.getConfigurations());}

3. getAutoConfigurationEntry 方法

protected AutoConfigurationEntry getAutoConfigurationEntry(        AnnotationMetadata annotationMetadata) {// 1. 检查是否开启自动配置if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;    }// 2. 获取注解属性(exclude, excludeName)AnnotationAttributesattributes= getAttributes(annotationMetadata);// 3. 从 spring.factories 加载所有候选配置类    List<String> configurations = getCandidateConfigurations(        annotationMetadata, attributes);// 4. 去重    configurations = removeDuplicates(configurations);// 5. 处理 exclude 排除项    Set<String> exclusions = getExclusions(annotationMetadata, attributes);    configurations.removeAll(exclusions);// 6. 条件过滤(@Conditional 系列注解)    configurations = filter(configurations, autoConfigurationMetadata);// 7. 触发自动配置导入事件    fireAutoConfigurationImportEvents(configurations, exclusions);returnnewAutoConfigurationEntry(configurations, exclusions);}

4. getCandidateConfigurations:加载 spring.factories

protected List<String> getCandidateConfigurations(        AnnotationMetadata metadata, AnnotationAttributes attributes) {// 关键:从 spring.factories 加载    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories");return configurations;}protected Class<?> getSpringFactoriesLoaderFactoryClass() {// 返回 EnableAutoConfiguration.classreturn EnableAutoConfiguration.class;}

SpringFactoriesLoader.loadFactoryNames 核心逻辑:

// SpringFactoriesLoader.javapublicstatic List<String> loadFactoryNames(        Class<?> factoryType, @Nullable ClassLoader classLoader) {StringfactoryTypeName= factoryType.getName();// 加载所有 META-INF/spring.factories 文件return loadSpringFactories(classLoader)        .getOrDefault(factoryTypeName, Collections.emptyList());}privatestatic Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {// 扫描所有 jar 包中的 META-INF/spring.factories    Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");// 读取每个文件,解析 key=value,合并成一个大 Map// key = 接口全限定名(如 EnableAutoConfiguration)// value = 实现类列表}

5. filter:条件过滤

private List<String> filter(        List<String> configurations,        AutoConfigurationMetadata autoConfigurationMetadata) {longstartTime= System.nanoTime();// 候选配置类列表    String[] candidates = StringUtils.toStringArray(configurations);// 是否需要跳过(条件过滤)boolean[] skip = newboolean[candidates.length];// 遍历每个配置类,检查条件for (inti=0; i < candidates.length; i++) {Stringcandidate= candidates[i];// 通过 AutoConfigurationMetadata 判断是否有条件注解if (autoConfigurationMetadata.wasProcessed(candidate)) {// 检查 @Conditional 条件是否满足            skip[i] = !autoConfigurationMetadata.getConditions(candidate).isEmpty();        }    }// 收集未被跳过的配置类    List<String> result = newArrayList<>(candidates.length);for (inti=0; i < candidates.length; i++) {if (!skip[i]) {            result.add(candidates[i]);        }    }return result;}

三、条件注解的工作原理

1. @Conditional 核心接口

// 条件接口@FunctionalInterfacepublicinterfaceCondition {booleanmatches(ConditionContext context, AnnotatedTypeMetadata metadata);}

2. 以 @ConditionalOnClass 为例

@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnClassCondition.class)// 关联条件类public@interface ConditionalOnClass {    Class<?>[] value() default {};    String[] name() default {};}

OnClassCondition 核心逻辑:

publicclassOnClassConditionextendsSpringBootCondition {@Overridepublic ConditionOutcome getMatchOutcome(            ConditionContext context, AnnotatedTypeMetadata metadata) {// 获取注解属性        MultiValueMap<String, Object> attributes =             metadata.getAllAnnotationAttributes(                ConditionalOnClass.class.getName(), true);// 获取需要的类名列表        List<String> onClasses = getClasses(attributes, "value");        List<String> onMissingClasses = getClasses(attributes, "name");// 检查这些类是否在 classpath 中存在ClassLoaderclassLoader= context.getClassLoader();// 所有需要的类都存在 → 条件满足// 有任何需要的类不存在 → 条件不满足returnnewConditionOutcome(match, message);    }}

3. 条件注解执行流程

自动配置类    │    ▼解析 @Conditional 注解    │    ▼实例化对应的 Condition 类(如 OnClassCondition)    │    ▼调用 matches() 方法    │    ├── 条件满足 → 加载配置类    │    └── 条件不满足 → 跳过配置类

四、Spring Boot 2.7 新特性:自动配置文件位置变化

Spring Boot 2.7 之前

META-INF/spring.factories

Spring Boot 2.7+(渐进废弃)

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

新格式:

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsorg.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfigurationorg.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigurationorg.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

一行一个配置类,更简洁。

五、自定义 Starter 实战

理解了自动配置原理,我们就能自己写 Starter 了。

1. 创建自动配置类

// 配置属性类@ConfigurationProperties(prefix = "hello")publicclassHelloProperties {privateStringprefix="Hello";privateStringsuffix="!";// getter/setter...}// 服务类publicclassHelloService {private String prefix;private String suffix;publicHelloService(String prefix, String suffix) {this.prefix = prefix;this.suffix = suffix;    }public String sayHello(String name) {return prefix + " " + name + suffix;    }}// 自动配置类@Configuration@ConditionalOnClass(HelloService.class)@EnableConfigurationProperties(HelloProperties.class)publicclassHelloAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic HelloService helloService(HelloProperties properties) {returnnewHelloService(properties.getPrefix(), properties.getSuffix());    }}

2. 创建 spring.factories(或新版 imports 文件)

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importscom.example.hello.HelloAutoConfiguration

3. 使用 Starter

引入依赖后,配置 application.yml

hello:prefix:"你好"suffix:"~"

使用:

@Autowiredprivate HelloService helloService;helloService.sayHello("老J");  // 你好 老J~

六、常见问题与面试回答

问题1:自动配置的执行顺序是怎样的?

面试回答: “AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,会在所有普通 @Configuration 类解析完成后才执行,这样用户配置可以覆盖自动配置。同时可以通过 @AutoConfigureOrder@AutoConfigureAfter@AutoConfigureBefore 来控制自动配置类之间的顺序。”

问题2:条件注解是怎样工作的?

面试回答: “每个 @Conditional 注解都关联一个 Condition 实现类。Spring Boot 在解析配置类时,会调用这些 Condition 的 matches() 方法判断条件是否满足。比如 @ConditionalOnClass 检查 classpath 中是否存在指定类,@ConditionalOnMissingBean 检查容器中是否已存在指定 Bean。”

问题3:如何自定义 Starter?

面试回答:

  1. 1. 写一个自动配置类,用 @ConfigurationProperties 定义配置属性
  2. 2. 用 @Conditional 控制加载条件
  3. 3. 在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中注册自动配置类
  4. 4. 在 spring.factories 中添加 EnableAutoConfiguration 的配置(2.7 之前的方式)

七、一张图总结

@SpringBootApplication        │        └── @EnableAutoConfiguration                │                └── @Import(AutoConfigurationImportSelector)                            │                            ▼                    selectImports()                            │                            ▼                    getAutoConfigurationEntry()                            │            ┌───────────────┼───────────────┐            ▼               ▼               ▼    加载 spring.    处理 exclude      @Conditional    factories        排除项           条件过滤            │               │               │            └───────────────┴───────────────┘                            │                            ▼                    返回符合条件的配置类                            │                            ▼                    Spring 容器注册这些配置类                            │                            ▼                    配置类内部的 @Bean 生效

八、下期预告

Spring Boot 源码解析(四):IoC 容器初始化深度剖析

  • • refresh() 方法 12 大步骤详解
  • • Bean 的创建流程
  • • 循环依赖解决原理(三级缓存)
  • • 面试必考知识点

九、互动时间

关于自动配置,你还有什么疑问?

  • • 条件注解不生效?
  • • 自定义 Starter 有问题?
  • • 配置顺序混乱?

评论区聊聊,老 J给你解答。


我是老 J,下期见。