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. 写一个自动配置类,用 @ConfigurationProperties定义配置属性2. 用 @Conditional控制加载条件3. 在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中注册自动配置类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,下期见。
夜雨聆风