SpringBoot(三)— 自动配置源码解析

​ 基于 Spring Boot 2.7.x 版本,深入剖析了 @EnableAutoConfiguration 注解的实现原理,重点解析了其背后通过 DeferredImportSelector 实现的自动配置机制。特别对 @AutoConfigureBefore@AutoConfigureAfter 所涉及的 DFS 拓扑排序逻辑 进行了详细的分析,揭示了自动配置类加载顺序的核心控制机制

​ 最后,还分析了 2.7.0 版本新增的 @AutoConfiguration 注解的特殊语义,包括其与新的 .imports SPI 文件的绑定默认排除扫描机制AutoConfigurationExcludeFilter)。并给出了自己的使用建议

@EnableAutoConfiguration

​ Spring Boot 之所以能实现开箱即用,核心在于其自动配置机制,具体体现在 @EnableAutoConfiguration 注解的作用上。该注解会为项目注册大量默认的 Bean,从而简化开发的配置工作

@EnableAutoConfiguration 本身并不直接生效,它是依赖了如下这个元注解(Spring 在处理配置类时会通过 AnnotationUtils递归解析所有注解的元注解,从而将所有元注解的功能集成到一个组合注解上

@Import(AutoConfigurationImportSelector.class)

​ 通过前面分析的章节,可知@Import注解会在ConfigurationClassPostProcessor解析配置类阶段生效。其注册的AutoConfigurationImportSelector这才是真正的获取自动配置类的核心,它实现了DeferredImportSelector

​ 与其它 @Import 导入的class不同,DeferredImportSelector会在所有常规的配置类(包括 @Configuration 的 full和@Component和lite模式)处理完成之后,再统一处理并解析其导入的相关配置类,这机制确保了自动配置不会抢占开发者自定义的配置解析和注册Bean的顺序,始终遵循用户配置优先原则

DeferredImportSelector

DeferredImportSelectorImportSelector 的一个子接口,它扩展了一个关键方法 getImportGroup(),用于支持按Group延迟导入配置类

getImportGroup()会返回 DeferredImportSelector.Group 对象,这才是真正处理用来DeferredImportSelector逻辑,这个Group包含了如下特征

  • Group加载的class可以理解为一个包,其中的类会在一起做过滤、排序等操作
  • 最终返回结果交给上层 ConfigurationClassParser 统一执行其配置类的解析

​ 到现在2.7.x版本位置,也就只有一个Group为AutoConfigurationImportSelector.AutoConfigurationGroup,是SpringBoot 为自动配置机制专门提供的 Group 实现,用于处理.imports和spring.factories里的自动配置类,并应用 AutoConfigurationImportFilter、排序等核心逻辑

AutoConfigurationImportSelector

其核心方法为getAutoConfigurationEntry,主要逻辑如下。在此之前,可以先看看SPI机制这篇文章

  1. 利用SPI机制,加载如下配置类

    1. META-INF/spring.factories配置文件里的org.springframework.boot.autoconfigure.EnableAutoConfiguration
    2. 从2.7.0新增的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports获取配置类
  2. 去重和排除excluded相关类

  3. 应用AutoConfigurationImportFilter逻辑,对自动配置类部分Condition进行校验并过滤(AutoConfigurationImportFilter实例也是配置在spring.factories里的)

核心代码

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        // 判断spring.boot.enableautoconfiguration配置是否开启
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 1. 从 META-INF/spring.factories配置文件里获取org.springframework.boot.autoconfigure.EnableAutoConfiguration
        // 2. 从2.7.0新增的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports获取配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去重
        configurations = removeDuplicates(configurations);
        // apply excluded逻辑
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        /*
         通过配置在spring.factories里的AutoConfigurationImportFilter过滤一遍,提前剔除明显不满足条件的配置类,提高启动性能。有如下三个Filter:
             1. org.springframework.boot.autoconfigure.condition.OnBeanCondition:apply @ConditionalOnSingleCandidate和@ConditionalOnBean逻辑
             2. org.springframework.boot.autoconfigure.condition.OnClassCondition:apply @ConditionalOnClass逻辑
             3. org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition:apply @ConditionalOnWebApplication逻辑
         */
        configurations = getConfigurationClassFilter().filter(configurations);
        // 发布AutoConfigurationImportEvent
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

}

AutoConfigurationGroup

是自动配置类导入的最终调度执行者,由 ConfigurationClassParser 在配置类解析流程的最后阶段统一触发,调用链如下::

  1. ConfigurationClassParser#parse
  2. ConfigurationClassParser.DeferredImportSelectorHandler#process
  3. ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
  4. ConfigurationClassParser.DeferredImportSelectorGrouping#getImports

其主要有两个方法:

  1. AutoConfigurationGroup#process:主要是利用AutoConfigurationImportSelector获取自动配置类,并缓存到其内部的entries中

  2. AutoConfigurationGroup#selectImports:使用AutoConfigurationSorter对自动配置类进行排序,排序的优先级为:

    @AutoConfigureBefore和@AutoConfigureAfter > @AutoConfigureOrder

核心代码

private static class AutoConfigurationGroup
        implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

    @Override
    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
        Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                () -> String.format("Only %s implementations are supported, got %s",
                        AutoConfigurationImportSelector.class.getSimpleName(),
                        deferredImportSelector.getClass().getName()));
        // 获取自动配置类并缓存到entries里
        AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                .getAutoConfigurationEntry(annotationMetadata);
        this.autoConfigurationEntries.add(autoConfigurationEntry);
        for (String importClassName : autoConfigurationEntry.getConfigurations()) {
            this.entries.putIfAbsent(importClassName, annotationMetadata);
        }
    }

    @Override
    public Iterable<Entry> selectImports() {
        if (this.autoConfigurationEntries.isEmpty()) {
            return Collections.emptyList();
        }
        Set<String> allExclusions = this.autoConfigurationEntries.stream()
                .map(AutoConfigurationEntry::getExclusions)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());
        Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                .map(AutoConfigurationEntry::getConfigurations)
                .flatMap(Collection::stream)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        // 排除excluded配置类
        processedConfigurations.removeAll(allExclusions);
        // 关键排序:@AutoConfigureBefore和@AutoConfigureAfter优先级 > @AutoConfigureOrder
        return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
                .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
                .collect(Collectors.toList());
    }

}

AutoConfigurationSorter

​ 自动配置类的解析顺序是非常重要的,尤其是在使用 @ConditionalOnBean@ConditionalOnMissingBean 等条件注解时,先加载的配置类有优先对Bean的注册决定权,从而影响后续自动配置类中相同类型Bean的注册。且由于所有自动配置类都统一通过 DeferredImportSelector 延迟导入,因此必须在导入前进行统一排序,保证顺序稳定且可控。

​ 所以,SpringBoot提供了如下三个注解,来决定自动配置类解析的先后顺序

  1. @AutoConfigureBefore:当前配置类应当 在指定类之前加载
  2. @AutoConfigureAfter:当前配置类应当 在指定类之后加载
  3. @AutoConfigureOrder:指定一个整数顺序值,数字越小,优先级越高

@AutoConfigureAfter@AutoConfigureBefore这两个注解构成了一个类之间的依赖图,SpringBoot 会对这些依赖关系进行拓扑排序。不同于常见的入度统计法,SpringBoot用了深度优先遍历(DFS)+ 判环的方式实现排序。

排序原理
  1. @AutoConfigureOrder 初步排序

    ​ 所有配置类根据注解的数值顺序进行一次粗排序,为后续 DFS 提供遍历顺序。

  2. 依赖链补齐

    ​ 收集所有声明在 @AutoConfigureBefore@AutoConfigureAfter 中提到的类(尽管它们可能被excluded),将其一并加入拓扑图,保证排序图完整

  3. 执行 DFS 拓扑排序(带判环)

    ​ 对每个配置类递归处理其前置依赖类(**@AutoConfigureAfter直接解析当前配置类就行,但@AutoConfigureBefore需要解析所有的自动配置类。我上一篇讲的starter优化器在这里会发挥作用,获取相关注解的预解析结果,间接提升启动速度),在递归中动态构造有向图,并检测循环依赖**(若存在环则抛出异常)

  4. 过滤出最终导入的配置类

    ​ 拓扑排序后将得到一个全有序列表,SpringBoot 再从中筛选出当前实际需要导入的类,作为最终结果返回。

核心代码
class AutoConfigurationSorter {

    List<String> getInPriorityOrder(Collection<String> classNames) {
        AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
                this.autoConfigurationMetadata, classNames);
        List<String> orderedClassNames = new ArrayList<>(classNames);
        // Initially sort alphabetically
        Collections.sort(orderedClassNames);
        // Then sort by order
        // 先按@AutoConfigureOrder排序
        orderedClassNames.sort((o1, o2) -> {
            int i1 = classes.get(o1).getOrder();
            int i2 = classes.get(o2).getOrder();
            return Integer.compare(i1, i2);
        });
        // Then respect @AutoConfigureBefore @AutoConfigureAfter
        orderedClassNames = sortByAnnotation(classes, orderedClassNames);
        return orderedClassNames;
    }

    private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {
        // 待排列表
        List<String> toSort = new ArrayList<>(classNames);
        // 加入所有参与排序的相关类。比如说某个配置类的@AutoConfigureBefore中的value不是配置类或者被excluded了,此时也要加入进来,使拓扑排序的链路更完整
        toSort.addAll(classes.getAllNames());
        // 已排序列表
        Set<String> sorted = new LinkedHashSet<>();
        // 处理中(检测循环依赖)
        Set<String> processing = new LinkedHashSet<>();
        // 开始排序
        while (!toSort.isEmpty()) {
            doSortByAfterAnnotation(classes, toSort, sorted, processing, null);
        }
        // 排序完毕,按顺序只保留classNames相关类
        sorted.retainAll(classNames);
        return new ArrayList<>(sorted);
    }

    /**
     * 拓扑排序的dfs实现方案(非统计入度方式实现)
     * 排序规则:
     *   1. 若类 A 上标注 @AutoConfigureAfter(B),则 B 必须排在 A 之前
     *   2. 若类 A 上标注 @AutoConfigureBefore(B),则 A 必须排在 B 之前
     */
    private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, Set<String> sorted,
            Set<String> processing, String current) {
        if (current == null) {
            current = toSort.remove(0);
        }
        processing.add(current);
        // 依次处理每个current类的依赖类(也就是说需要确保current 排在 after 之后)
        for (String after : classes.getClassesRequestedAfter(current)) {
            // 拓扑排序中判环操作,避免循环依赖
            checkForCycles(processing, current, after);
            // 只有当该类还没被排序,且在待排序列表中,才递归处理依赖类after
            if (!sorted.contains(after) && toSort.contains(after)) {
                doSortByAfterAnnotation(classes, toSort, sorted, processing, after);
            }
        }
        processing.remove(current);
        // current的依赖都已处理完毕,此时可以放入current了
        sorted.add(current);
    }

    private void checkForCycles(Set<String> processing, String current, String after) {
        Assert.state(!processing.contains(after),
                () -> "AutoConfigure cycle detected between " + current + " and " + after);
    }

    private static class AutoConfigurationClasses {

        /**
         * 获取某个类的所有排序依赖类(即必须排在className之前的类)
         */
        Set<String> getClassesRequestedAfter(String className) {
            // @AutoConfigureAfter处理
            Set<String> classesRequestedAfter = new LinkedHashSet<>(get(className).getAfter());
            // @AutoConfigureBefore只能遍历所有的配置类,依次判断是否包含当前的className
            this.classes.forEach((name, autoConfigurationClass) -> {
                if (autoConfigurationClass.getBefore().contains(className)) {
                    classesRequestedAfter.add(name);
                }
            });
            return classesRequestedAfter;
        }

    }

}

@AutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {
  // ... 省略
}

是2.7.0版本新增的自动配置相关的注解,专门用于声明自动配置类。从定义上看,它是一个组合注解,整合了:

  • @Configuration:标识该类是一个配置类
  • @AutoConfigureBefore / @AutoConfigureAfter:用于声明自动配置类之间的依赖顺序

但它并不只是简单的注解组合,而是具备了特殊语义

特殊语义

新的SPI

​ 支持新的 SPI 加载机制(.imports 文件),可以从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里加载自动配置类。从另一方面来说,其把自动配置类从spring.factories文件里解耦出来了

AutoConfigurationExcludeFilter

@SpringBootApplication有如下这个元注解,对包扫描器增加了AutoConfigurationExcludeFilter这个过滤器

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

AutoConfigurationExcludeFilter会自动排出满足一下任意条件的类:

  1. 有@AutoConfiguration注解
  2. SPI文件里的自动配置类

也就是说:所有使用 @AutoConfiguration 的类将被默认包扫描排除,不会参与常规的组件扫描逻辑

核心代码
private boolean isAutoConfiguration(MetadataReader metadataReader) {
    // @AutoConfiguration注解是否存在
    boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata()
            .isAnnotated(AutoConfiguration.class.getName());
    return annotatedWithAutoConfiguration
            || getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}

protected List<String> getAutoConfigurations() {
    if (this.autoConfigurations == null) {
        // spring.factories里的自动配置类
        List<String> autoConfigurations = new ArrayList<>(
                SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));
        // .imports文件里的自动配置类
        ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader).forEach(autoConfigurations::add);
        this.autoConfigurations = autoConfigurations;
    }
    return this.autoConfigurations;
}

总结

​ 从源码来反推@AutoConfiguration的使用场景,可以得出一个明确的使用约定:

@AutoConfiguration 注解的类必须通过 SPI文件注册,不能依赖常规的包扫描导入。

​ 无论该类是来自第三方 starter 还是当前项目模块,只要标注了 @AutoConfiguration,就不会被@SpringBootApplication 自动注册,而只能走自动配置的延迟导入机制(DeferredImportSelector)流程。

​ 所以,在当前SpringBoot启动项目中,谨慎使用@AutoConfiguration,尽管它内部组合了 @Configuration,但它不等价于普通的配置类

相关链接