购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 AutoConfigurationImportSelector源码解析

@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。@Import(AutoConfigurationImportSele-ctor.class)又可以分为两部分:@Import和对应的ImportSelector。本节重点讲解@Import的基本使用方法和ImportSelector的实现类AutoConfigurationImportSelector。

2.3.1 @Import注解

@Import注解位于spring-context项目内,主要提供导入配置类的功能。为什么要专门讲解@Import的功能及使用呢?如果查看Spring Boot的源代码,我们会发现大量的EnableXXX类都使用了该注解。了解@Import注解的基本使用方法,能够帮助我们更好地进行源代码的阅读和理解。

@Import的源码如下。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

@Import的作用和xml配置中<import/>标签的作用一样,我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO(将其注册成Spring Bean,导入POJO需要Spring 4.2以上版本)。

关于@Import导入@Configuration注解类和POJO的功能比较简单和常见,就不再展开介绍了。下面重点介绍ImportSelector接口的作用。

2.3.2 ImportSelector接口

@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。ImportSelector接口源码如下。


public interface ImportSelector {
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。

如果实现了接口ImportSelector的类的同时又实现了以下4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为:EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware和ResourceLoaderAware。

在AutoConfigurationImportSelector的源代码中就实现了这4个接口,部分源代码及Aware的全限定名代码如下。


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;

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

在上面的源代码中,AutoConfigurationImportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredImportSelector。DeferredImportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前先去加载返回的配置类。

DeferredImportSelector的加载顺序可以通过@Order注解或实现Ordered接口来指定。同时,DeferredImportSelector提供了新的方法getImportGroup()来跨DeferredImportSelector实现自定义Configuration的加载顺序。

2.3.3 AutoConfigurationImportSelector功能概述

下面我们通过图2-3所示的流程图来从整体上了解AutoConfigurationImportSelector的核心功能及流程,然后再对照代码看具体的功能实现。图2-3中省略了外部通过@Import注解调用该类的部分。

图2-3 AutoConfigurationImportSelector核心功能及流程图

当AutoConfigurationImportSelector被@Import注解引入之后,它的selectImports方法会被调用并执行其实现的自动装配逻辑。读者朋友需注意,selectImports方法几乎涵盖了组件自动装配的所有处理逻辑。AutoConfigurationImportSelector的selectImports方法源代码如下:


@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 检查自动配置功能是否开启,默认为开启
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 加载自动配置的元信息,配置文件为类路径中META-INF目录下的
    // spring-autoconfigure-metadata.properties文件
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    // 封装将被引入的自动配置信息
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
        autoConfigurationMetadata, annotationMetadata);
    // 返回符合条件的配置类的全限定名数组
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 通过SpringFactoriesLoader类提供的方法加载类路径中META-INF目录下的
    // spring.factories文件中针对EnableAutoConfiguration的注册配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
        attributes);
    // 对获得的注册配置类集合进行去重处理,防止多个项目引入同样的配置类
    configurations = removeDuplicates(configurations);
    // 获得注解中被exclude或excludeName所排除的类的集合
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件则抛出异常
    checkExcludedClasses(configurations, exclusions);
    // 从自动配置类集合中去除被排除的类
    configurations.removeAll(exclusions);
    // 检查配置类的注解是否符合spring.factories文件中AutoConfigurationImportFilter指定的注解检查条件
    configurations = filter(configurations, autoConfigurationMetadata);
    // 将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器。监听器的配置在于spring.factories文件中,通过AutoConfigurationImportListener指定
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

通过图2-3和上述代码,我们从整体层面了解了AutoConfigurationImportSelector的概况及操作流程,后文将对这些流程进行细化拆分,并通过阅读源代码来分析Spring Boot是如何实现自动加载功能的。

2.3.4 @EnableAutoConfiguration自动配置开关

检查自动配置是否开启的代码位于AutoConfigurationImportSelector的selectImports方法第一段中。如果开启自动配置功能,就继续执行后续操作;如果未开启,就返回空数组。代码如下。


@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
...
}

该方法主要使用isEnabled方法来判断自动配置是否开启,代码如下。


protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty(
            EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
            true);
    }
    return true;
}

通过isEnabled方法可以看出,如果当前类为AutoConfigurationImportSelector,程序会从环境中获取key为EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的配置,该常量的值为spring.boot.enableautoconfiguration。如果获取不到该属性的配置,isEnabled默认为true,也就是默认会使用自动配置。如果当前类为其他类,直接返回true。

如果想覆盖或重置EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的配置,可获取该常量的值,并在application.properties或application.yml中针对此参数进行配置。以application.properties配置关闭自动配置为例,代码如下。


spring.boot.enableautoconfiguration=false

2.3.5 @EnableAutoConfiguration加载元数据配置

加载元数据配置主要是为后续操作提供数据支持。我们先来看加载相关源代码的具体实现,该功能的代码依旧在selectImports方法内。


@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    ...
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    ...
}

加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下META-INF/spring-autoconfigure-metadata.properties内的配置。


final class AutoConfigurationMetadataLoader {
    // 默认加载元数据的路径
    protected static final String PATH = "META-INF/spring-autoconfigure-metadata.
properties";

    // 默认调用改方法,传入默认PATH
    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, PATH);
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String 
path) {
        try {
            // 获取数据存储于Enumeration中
            Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources
(path)
                    : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();
            while (urls.hasMoreElements()) {
                // 遍历Enumeration中的URL,加载其中的属性,存储到Properties中
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
            }
            return loadMetadata(properties);
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
        }
    }

    // 创建AutoConfigurationMetadata的实现类PropertiesAutoConfigurationMetadata
    static AutoConfigurationMetadata loadMetadata(Properties properties) {
        return new PropertiesAutoConfigurationMetadata(properties);
    }

    // AutoConfigurationMetadata的内部实现类
    private static class PropertiesAutoConfigurationMetadata implements AutoCon-
figurationMetadata {
    ...
    }
    ...
}

在上面的代码中,AutoConfigurationMetadataLoader调用loadMetadata(ClassLoader classLoader)方法,会获取默认变量PATH指定的文件,然后加载并存储于Enumeration数据结构中。随后,从变量PATH指定的文件中获取其中配置的属性存储于Properties内,最终调用在该类内部实现的AutoConfigurationMetadata的子类的构造方法。

spring-autoconfigure-metadata.properties文件内的配置格式如下。


自动配置类的全限定名.注解名称=值

如果spring-autoconfigure-metadata.properties文件内有多个值,就用英文逗号分隔,例如:


...
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration.ConditionalOnClass=org.springframework.data.jdbc.repository.config.JdbcConfiguration,org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations
...

为什么要加载此元数据呢?加载元数据主要是为了后续过滤自动配置使用。Spring Boot使用一个Annotation的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。Spring Boot会将收集好的@Configuration进行一次过滤,进而剔除不满足条件的配置类。

在官方文档中已经明确指出,使用这种配置方式可以有效缩短Spring Boot的启动时间,减少@Configuration类的数量,从而减少初始化Bean的耗时。后续章节中我们会看到过滤自动配置的具体使用方法。

2.3.6 @EnableAutoConfiguration加载自动配置组件

加载自动配置组件是自动配置的核心组件之一,这些自动配置组件在类路径中META-INF目录下的spring.factories文件中进行注册。Spring Boot预置了一部分常用组件,如果我们需要创建自己的组件,可参考Spring Boot预置组件在自己的Starters中进行配置,在后面的章节中会专门对此进行讲解。

通过Spring Core提供的SpringFactoriesLoader类可以读取spring.factories文件中注册的类。下面我们通过源代码来看一下如何在AutoConfigurationImportSelector类中通过getCandidateConfigurations方法来读取spring.factories文件中注册的类。


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations,
        "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

getCandidateConfigurations方法使用SpringFactoriesLoader类提供的loadFactoryNames方法来读取META-INF/spring.factories中的配置。如果程序未读取到任何配置内容,会抛出异常信息。而loadFactoryNames方法的第一个参数为getSpringFactoriesLoaderFactoryClass方法返回的EnableAutoConfiguration.class,也就是说loadFactoryNames只会读取配置文件中针对自动配置的注册类。

SpringFactoriesLoader类的loadFactoryNames方法相关代码如下。


public final class SpringFactoriesLoader {

    // 概类加载文件的路径,可能存在多个
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    ...
    // 加载所有的META-INF/spring.factories文件,封装成Map,并从中获取指定类名的列表
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 
Collections.emptyList());
    }
    // 加载所有的META-INF/spring.factories文件,封装成Map,Key为接口的全类名,Value为对应配置值的List集合
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListTo-
StringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
...
}

简单描述以上加载的过程就是:SpringFactoriesLoader加载器加载指定ClassLoader下面的所有META-INF/spring.factories文件,并将文件解析内容存于Map<String,List<String>>内。然后,通过loadFactoryNames传递过来的class的名称从Map中获得该类的配置列表。

结合下面spring.factories文件的内容格式,我们可以更加清晰地了解Map<String,List<String>>中都存储了什么。


...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfig-
uration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
...

以上代码仅以EnableAutoConfiguration配置的部分内容为例,spring.factories文件的基本格式为自动配置类的全限定名=值,与2.3.5节中介绍的元数据的格式很相似,只不过缺少了“.注解名称”部分,如果包含多个值,用英文逗号分隔。

我们继续以EnableAutoConfiguration的配置为例,Map<String,List<String>>内存储的对应数据就是key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration,Value值为其等号后面以分号分割的各种AutoConfiguration类。

当然,spring.factories文件内还有其他的配置,比如用于监听的Listeners和用于过滤的Filters等。很显然,在加载自动配置组件时,此方法只用到了EnableAutoConfiguration对应的配置。

因为程序默认加载的是ClassLoader下面的所有META-INF/spring.factories文件中的配置,所以难免在不同的jar包中出现重复的配置。我们可以在源代码中使用Set集合数据不可重复的特性进行去重操作。


protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}

2.3.7 @EnableAutoConfiguration排除指定组件

在2.3.6节中我们获得了spring.factories文件中注册的自动加载组件,但如果在实际应用的过程中并不需要其中的某个或某些组件,可通过配置@EnableAutoConfiguration的注解属性exclude或excludeName进行有针对性的排除,当然也可以通过配置文件进行排除。先通过源代码看看如何获取排除组件的功能。


protected Set<String> getExclusions(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    // 创建Set集合并把待排除的内容存于集合内,LinkedHashSet具有不可重复性
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

private List<String> getExcludeAutoConfigurationsProperty() {
    if (getEnvironment() instanceof ConfigurableEnvironment) {
        Binder binder = Binder.get(getEnvironment());
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
            .map(Arrays::asList).orElse(Collections.emptyList());
    }
    String[] excludes = getEnvironment()
            .getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

AutoConfigurationImportSelector中通过调用getExclusions方法来获取被排除类的集合。它会收集@EnableAutoConfiguration注解中配置的exclude属性值、excludeName属性值,并通过方法getExcludeAutoConfigurationsProperty获取在配置文件中key为spring.autoconfigure.exclude的配置值。以排除自动配置DataSourceAutoConfiguration为例,配置文件中的配置形式如下。


spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSource-
AutoConfiguration

获取到被排除组件的集合之后,首先是对待排除类进行检查操作,代码如下。


private void checkExcludedClasses(List<String> configurations,
        Set<String> exclusions) {
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    // 遍历并判断是否存在对应的配置类
    for (String exclusion : exclusions) {
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader())
                && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
        }
    }
    // 如果不为空,就进行处理
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}

// 抛出指定异常
protected void handleInvalidExcludes(List<String> invalidExcludes) {
    StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {
        message.append("\t- ").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String
            .format("The following classes could not be excluded because they are"
                + " not auto-configuration classes:%n%s", message));
}

以上代码中,checkExcludedClasses方法用来确保被排除的类存在于当前的ClassLoader中,并且包含在spring.factories注册的集合中。如果不满足以上条件,调用handleInvalidExcludes方法抛出异常。

如果被排除类都符合条件,调用configurations.removeAll(exclusions)方法从自动配置集合中移除被排除集合的类,至此完成初步的自动配置组件排除。

2.3.8 @EnableAutoConfiguration过滤自动配置组件

当完成初步的自动配置组件排除工作之后,AutoConfigurationImportSelector会结合在此之前获取的AutoConfigurationMetadata对象,对组件进行再次过滤。


private List<String> filter(List<String> configurations,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilte-
rs()) {
        invokeAwareMethods(filter);
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    ...
    return new ArrayList<>(result);
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
            this.beanClassLoader);
}

下面,我们先来明确一下都有哪些数据参与了以上两种方法,然后再进行业务逻辑的梳理。

·configurations:List,经过初次过滤之后的自动配置组件列表。

·autoConfigurationMetadata:AutoConfigurationMetadata,元数据文件META-INF/spring-autoconfigure-metadata.properties中配置对应实体类。

·List:META-INF/spring.factories中配置key为Auto-ConfigurationImportFilter的Filters列表。

getAutoConfigurationImportFilters方法是通过SpringFactoriesLoader的loadFactories方法将META-INF/spring.factories中配置key为AutoConfigurationImportFilter的值进行加载。下面为META-INF/spring.factories中相关配置的具体内容。


# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

在spring-boot-autoconfigure中默认配置了3个筛选条件,OnBeanCondition、OnClassCondition和OnWebApplicationCondition,它们均实现了AutoConfigurationImportFilter接口。

在明确了以上信息之后,该filter方法的过滤功能就很简单了。用一句话来概述就是:对自动配置组件列表进行再次过滤,过滤条件为该列表中自动配置类的注解得包含在OnBeanCondition、OnClassCondition和OnWebApplicationCondition中指定的注解,依次包含@ConditionalOnBean、@ConditionalOnClass和@ConditionalOnWebApplication。

那么在这个实现过程中,AutoConfigurationMetadata对应的元数据和AutoConfiguration-ImportFilter接口及其实现类是如何进行具体筛选的呢?我们先来看一下AutoConfiguration-ImportFilter接口相关类的结构及功能,如图2-4所示。

图2-4 AutoConfigurationImportFilter接口相关类的结构及功能实现图

下面进行相关的源代码及步骤的分解。我们已经知道AutoConfigurationImportFilter接口可以在spring.factories中注册过滤器,用来过滤自动配置类,在实例化之前快速排除不需要的自动配置,代码如下。


@FunctionalInterface
public interface AutoConfigurationImportFilter {
    boolean[] match(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata);
}

match方法接收两个参数,一个是待过滤的自动配置类数组,另一个是自动配置的元数据信息。match返回的结果为匹配过滤后的结果布尔数组,数组的大小与String[ ] autoConfigurationClasses一致,如果需排除,设置对应值为false。

图2-4中已经显示AutoConfigurationImportFilter接口的match方法主要在其抽象子类中实现,而抽象子类FilteringSpringBootCondition在实现match方法的同时又定义了新的抽象方法getOutcomes,继承该抽象类的其他3个子类均实现了getOutcomes方法,代码如下。


abstract class FilteringSpringBootCondition extends SpringBootCondition
        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoader
Aware {
    ...
    @Override
    public boolean[] match(String[] autoConfigurationClasses,
            AutoConfigurationMetadata autoConfigurationMetadata) {
        ...
        ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
            autoConfigurationMetadata);
        boolean[] match = new boolean[outcomes.length];
        for (int i = 0; i < outcomes.length; i++) {
            match[i] = (outcomes[i] == null || outcomes[i].isMatch());
            ...
        }
        return match;
    }

    // 过滤核心功能,该方法由子类实现
    protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigur
ationClasses,
         AutoConfigurationMetadata autoConfigurationMetadata);
    ...
}

通过上面的源码可以看出,match方法在抽象类FilteringSpringBootCondition中主要的功能就是调用getOutcomes方法,并将其返回的结果转换成布尔数组。而相关的过滤核心功能由子类实现的getOutcomes方法来实现。

下面以实现类OnClassCondition来具体说明执行过程。首先看一下入口方法getOutcomes的源代码。


@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {

    @Override
    protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
            AutoConfigurationMetadata autoConfigurationMetadata) {
        // 如果有多个处理器,采用后台线程处理
        if (Runtime.getRuntime().availableProcessors() > 1) {
            return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigu-
rationMetadata);
        } else {
        OutcomesResolver outcomesResolver = new 
        StandardOutcomesResolver(autoConfigurationClasses, 0,
            autoConfigurationClasses.length, autoConfigurationMetadata, 
            getBeanClassLoader());
        return outcomesResolver.resolveOutcomes();
        }
    }
    ...
}

Spring Boot当前版本对getOutcomes方法进行了性能优化,根据处理器的情况不同采用了不同的方式进行操作。如果使用多个处理器,采用后台线程处理(之前版本的实现方法)。否则,getOutcomes直接创建StandardOutcomesResolver来处理。

在resolveOutcomesThreaded方法中主要采用了分半处理的方法来提升处理效率,而核心功能都是在内部类StandardOutcomesResolver的resolveOutcomes方法中实现。

resolveOutcomesThreaded的分半处理实现代码如下。


@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
    private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
            AutoConfigurationMetadata autoConfigurationMetadata) {
        int split = autoConfigurationClasses.length / 2;
        OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigu-
rationClasses, 0, split, autoConfigurationMetadata);
        OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(au-
toConfigurationClasses, split,
            autoConfigurationClasses.length, autoConfigurationMetadata, getBean-
ClassLoader());
        ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
        ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
        ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClas-
ses.length];
        System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
        System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
        return outcomes;
    }
}

内部类StandardOutcomesResolver的源代码重点关注getOutcomes方法的实现,它实现了获取元数据中的指定配置,间接调用getOutcome(String className,ClassLoader classLoader)方法来判断该类是否符合条件,部分源代码如下。


@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
    ...
    private final class StandardOutcomesResolver implements OutcomesResolver {
        ...
        private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
                int start, int end, AutoConfigurationMetadata autoConfiguration-
Metadata) {
            ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
            for (int i = start; i < end; i++) {
                String autoConfigurationClass = autoConfigurationClasses[i];
                if (autoConfigurationClass != null) {
                    String candidates = autoConfigurationMetadata
                        .get(autoConfigurationClass, "ConditionalOnClass");
                if (candidates != null) {
                        outcomes[i - start] = getOutcome(candidates);
                    }
                }
            }
            return outcomes;
        }
        ...
        // 判断该类是否符合条件
        private ConditionOutcome getOutcome(String className, ClassLoader class-
Loader) {
            if (ClassNameFilter.MISSING.matches(className, classLoader)) {
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnClass.class)
                        .didNotFind("required class").items(Style.QUOTE, className));
            }
            return null;
        }
    }
}

在获取元数据指定配置的功能时用到了AutoConfigurationMetadata接口的get(String className,String key)方法,而该方法由类AutoConfigurationMetadataLoader来实现。该类在前面的章节已经提过了,它会加载META-INF/spring-autoconfigure-metadata.properties中的配置。


final class AutoConfigurationMetadataLoader {

    protected static final String PATH = "META-INF/"
            + "spring-autoconfigure-metadata.properties";
    ...
    private static class PropertiesAutoConfigurationMetadata
            implements AutoConfigurationMetadata {
        ...
        @Override
        public String get(String className, String key) {
            return get(className, key, null);
        }

        @Override
        public String get(String className, String key, String defaultValue) {
        String value = this.properties.getProperty(className + "." + key);
            return (value != null) ? value : defaultValue;
        }
    }
}

AutoConfigurationMetadataLoader的内部类PropertiesAutoConfigurationMetadata实现了AutoConfigurationMetadata接口的具体方法,其中包含我们用到的get(String className,String key)方法。

根据get方法实现过程,我们不难发现,在getOutcomes方法中获取到的candidates,其实就是META-INF/spring-autoconfigure-metadata.properties文件中配置的key为自动加载注解类+“.”+“ConditionalOnClass”的字符串,而value为其获得的值。以数据源的自动配置为例,寻找到的对应元数据配置如下。


org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.
EmbeddedDatabaseType

key为自动加载组件org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,加上“.”,再加上当前过滤条件中指定的ConditionalOnClass。然后,根据此key获得的value值为javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType。

当获取到对应的candidates值之后,最终会调用getOutcome(String className,ClassLoader classLoader)方法,并在其中使用枚举类ClassNameFilter.MISSING的matches方法来判断candidates值是否匹配。而枚举类ClassNameFilter位于OnClassCondition继承的抽象类FilteringSpringBootCondition中。


abstract class FilteringSpringBootCondition extends SpringBootCondition
        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
    ...
    protected enum ClassNameFilter {
        PRESENT {
            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
            }
        },
        MISSING {
            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }
        };

        public abstract boolean matches(String className, ClassLoader classLoader);

        // 通过类加载是否抛出异常来判断该类是否存在
        public static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            } try {
                forName(className, classLoader);
                return true;
            } catch (Throwable ex) {
                return false;
            }
        }

        // 进行类加载操作
        private static Class<?> forName(String className, ClassLoader classLoader)
                throws ClassNotFoundException {
            if (classLoader != null) {
                return classLoader.loadClass(className);
            }
            return Class.forName(className);
        }
    }
}

ClassNameFilter的匹配原则很简单,就是通过类加载器去加载指定的类。如果指定的类加载成功,即没有抛出异常,说明ClassNameFilter匹配成功。如果抛出异常,说明ClassNameFilter匹配失败。

至此,整个过滤过程的核心部分已经完成了。我们再用一张简单的流程图来回顾整个过滤的过程,如图2-5所示。

图2-5 AutoConfigurationImportFilter接口相关功能及实现类流程图

2.3.9 @EnableAutoConfiguration事件注册

在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播,相关代码如下。


private void fireAutoConfigurationImportEvents(List<String> configurations,
        Set<String> exclusions) {
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

protected List<AutoConfigurationImportListener> getAutoConfigurationImportLis-
teners() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
            this.beanClassLoader);
}

以上代码首先通过SpringFactoriesLoader类提供的loadFactories方法将spring.factories中配置的接口AutoConfigurationImportListener的实现类加载出来。然后,将筛选出的自动配置类集合和被排除的自动配置类集合封装成AutoConfigurationImportEvent事件对象,并传入该事件对象通过监听器提供的onAutoConfigurationImportEvent方法,最后进行事件广播。关于事件及事件监听相关的内容不在此过多展开。

spring.factories中自动配置监听器相关配置代码如下。 qZfQ5BmJwVh2R/oT6KWilCxlLQuAY/sZLlIPcPNP8YPthGidoM6dBof8BLrWKDvf


org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAuto-
ConfigurationImportListener

点击中间区域
呼出菜单
上一章
目录
下一章
×