【Spring Boot 源码学习】走近 AutoConfigurationImportSelector

2024-09-06 07:30:34 浏览数 (1)

引言

上篇博文我们了解了 @EnableAutoConfiguration 注解,其中真正实现自动配置功能的核心实现者 AutoConfigurationImportSelector 还没有详细说明,本篇将从它的源码入手来重点介绍。

主要内容

在介绍 AutoConfigurationImportSelector 之前,有必要了解下它所实现的 ImportSelector 接口 ,如下所示:

1. ImportSelector 接口

在上篇博文中,我们介绍过 @Import 注解,它的许多功能其实是需要 ImportSelector 接口来实现,ImportSelector 接口决定可引入哪些 @Configuration

下面我们来看一下 ImportSelector 接口的源码【spring-context:5.3.25】:

代码语言:java复制
/**
 * 实现了确定基于给定选择条件应该导入哪些 @Configuration 类的类型的接口,
 * 通常是一个或多个注解属性。
 * 
 * 一个 ImportSelector 可以实现以下任意一个 Aware 接口,并在调用 selectImports 
 * 方法之前调用其对应的方法:
 *    EnvironmentAware
 *    BeanFactoryAware
 *    BeanClassLoaderAware
 *    ResourceLoaderAware
 * 
 * 另外,该类也可以提供一个带有以下支持的参数类型的单个构造函数:
 *    Environment
 *    BeanFactory
 *    ClassLoader
 *    ResourceLoader
 * 
 * ImportSelector 实现通常与普通的 @Import 注解一样进行处理。
 * 然而,还可以推迟选择要导入的内容,直到所有 @Configuration 
 * 类都被处理完毕(详见 DeferredImportSelector)。
 *
 */
public interface ImportSelector {

    /**
     * 根据导入的 @Configuration 类的 AnnotationMetadata(注解元数据),
     * 选择并返回应该导入的类名称。
     * 
     * @return 返回类名的数组,如果没有则返回空数组。
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    /**
     * 返回一个用于从导入的候选类中排除类的断言函数,
     * 该函数会递归地应用于通过此选择器的导入项找到的所有类。
     * 
     * 如果对于给定的完全限定类名,该断言函数返回 true,
     * 则该类将不被视为被导入的配置类,从而跳过类文件加载和元数据检查。
     * 
     * @return 返回一个用于完全限定的候选类名的筛选断言函数,该函数适用于递归导入的配置类。
     * 如果没有筛选断言函数,则返回 null。
     * @since 5.2.4
     */
    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }

}

通过阅读上述源码,我们可以看到 ImportSelector 接口提供了一个参数为 AnnotationMetadata【它里面包含了被 @Import 注解的类的注解信息,即注解元数据】 的方法 selectImports ,并返回了一个字符串数组【可以根据具体实现决定返回哪些配置类的全限定名】。

源码注释里也提到了,如果我们实现了 ImportSelector 接口的同时又实现了以下的 4Aware 接口,那么 Spring 保证在调用 ImportSelector 之前会先调用 Aware 接口的方法。

这 4 个 Aware 接口分别是:

  • EnvironmentAware
  • BeanFactoryAware
  • BeanClassLoaderAware
  • ResourceLoaderAware

我们本篇要重点进行源码解析的 AutoConfigurationImportSelector 就实现了上述 4Aware 接口,部分源码如下所示:

代码语言:java复制
// 其他导入语句省略
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;

/**
 * DeferredImportSelector 用于处理自动配置的延迟导入选择器。
 * 如果需要自定义的 @EnableAutoConfiguration 变体,也可以通过继承这个类来实现。
 */
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    // 其他省略
}

从上面的 类定义中,我们可以看到 AutoConfigurationImportSelector 并没有直接实现 ImportSelector 接口,而是实现了 DeferredImportSelector 接口【它是 ImportSelector 的子接口 】。

2. DeferredImportSelector 接口

AutoConfigurationImportSelector 为啥不直接实现 ImportSelector 接口,而是实现了 DeferredImportSelector 接口呢?它们俩有什么区别呢?

在讲解清楚之前,我们先来看看 DeferredImportSelector 接口的源码【spring-context:5.3.25】:

代码语言:java复制
/**
 * 一种在所有 @Configuration bean 处理完毕后运行的 ImportSelector 变体。
 * 这种类型的选择器在所选的导入项带有条件时特别有用。
 * 
 * 实现类可以扩展 org.springframework.core.Ordered 接口或使用 
 * org.springframework.core.annotation.Order 注解来指定与
 * 其他 DeferredImportSelectors 的优先级。
 * 
 * 实现类还可以提供一个导入组(import group),
 * 它可以在不同的选择器之间提供额外的排序和过滤逻辑。
 */
public interface DeferredImportSelector extends ImportSelector {

    /**
     * 返回一个特定的导入组。
     * 默认实现会在不需要分组的情况下返回 null。
     * 
     * @return 导入组的类,如果没有则返回 null。
     * @since 5.0
     */
    @Nullable
    default Class<? extends Group> getImportGroup() {
        return null;
    }


    /**
     * 用于将来自不同导入选择器的结果进行分组的接口。
     * 
     * @since 5.0
     */
    interface Group {

        /**
         * 使用指定的 DeferredImportSelector 处理导入的 @Configuration 类的 AnnotationMetadata。
         */
        void process(AnnotationMetadata metadata, DeferredImportSelector selector);

        /**
         * 返回此组应该导入的类的条目
         */
        Iterable<Entry> selectImports();


        /**
         * 一个条目,包含导入的配置类的 AnnotationMetadata 和要导入的类名。
         */
        class Entry {

            private final AnnotationMetadata metadata;

            private final String importClassName;

            public Entry(AnnotationMetadata metadata, String importClassName) {
                this.metadata = metadata;
                this.importClassName = importClassName;
            }

            /**
             * 返回导入的配置类的 AnnotationMetadata【注解元数据】
             */
            public AnnotationMetadata getMetadata() {
                return this.metadata;
            }

            /**
             * 返回要导入的类的完全限定名称。
             */
            public String getImportClassName() {
                return this.importClassName;
            }

            // 省略。。。
        }
    }

}

通过阅读上述源码,可以了解到之所以 AutoConfigurationImportSelector 没有直接实现 ImportSelector 接口,而是实现了 DeferredImportSelector 接口,是因为通过DeferredImportSelector 接口能够在处理自动配置时,拥有更高的灵活性和可定制性。

总结来讲,它们的区别主要是如下几个方面:

  • 延迟导入DeferredImportSelector 具有延迟导入的能力,可以在所有的 @Configuration 类都被处理完毕之后再进行选择和导入。这样可以在整个配置加载过程完成后再根据某些条件或规则来决定要导入哪些类,从而实现更加动态和灵活的自动配置机制。
  • 自定义扩展:通过实现 DeferredImportSelector 接口,开发人员可以更方便地扩展和定制自动配置逻辑。可以根据实际需求重写相应方法,实现自定义的自动配置规则和行为。

上述源码注释中,也说明了 DeferredImportSelector 的加载顺序可以通过 @Order 注解 或 实现 Ordered 接口来指定。它还可以提供一个导入组,实现在不同的选择器之间提供额外的排序和过滤逻辑,从而实现自定义 Configuration 的加载顺序。

3. AutoConfigurationImportSelector 功能概述

好了到这里,我们终于可以开始正式介绍 AutoConfigurationImportSelector 了。

下面我们通过如下的流程图,从整体上来了解 AutoConfigurationImportSelector 的核心功能及流程【其中省略了外部通过 @Import 注解调用该类的部分】:

AutoConfigurationImportSelector@Import 注解引入之后,它的 selectImports 方法会被调用并执行其实现的自动装配逻辑。

下面我们来看看 selectImports 方法的源码,如下所示 【Spring Boot 2.0.9.RELEASE】:

代码语言:java复制
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return StringUtils.toStringArray(configurations);
    }

注意: 随着 Spring Boot 的版本演进,自动配置类加载的方式与上述有所区别。 虽然不从 selectImports 进入,但是核心代码不变。

如下 getAutoConfigurationEntry 中所示【Spring Boot 2.7.9】:

代码语言:java复制
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

总结

通过阅读上述源码,对照相关的流程图,我们从整体上了解了 AutoConfigurationImportSelector 自动装配逻辑的核心功能及流程,由于篇幅有限,更加细化的功能及流程解析,笔者将在后续的博文中,带大家一起通过源码来一步步完成,敬请期待!!!

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

0 人点赞