Spring Boot自动配置的原理

Spring Boot以零配置、开箱即用的特性而闻名。开发者只需在主类上添加@SpringBootApplication注解,然后在外化配置文件(比如application.yml)中创建独立的配置,就能成功启动应用,避免了传统SSM应用中的繁琐的XML配置。这样的便利性其实是建立在Spring Boot强大的自动配置机制上实现的,本文就来分析一下Spring Boot中自动配置的原理。


@SpringBootApplication注解

一般我们都会在项目的主类上添加该注解,而这就激活了Spring Boot的自动配置,这个注解为什么这么神奇?

v2.7.x
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // 开启自动配置
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

重点是该注解中引入了一个关键注解:@EnableAutoConfiguration。顾名思义,这个注解就是用来开启自动配置的。

v2.7.x
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfiguration.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	/**
	 * Environment property that can be used to override when auto-configuration is
	 * enabled.
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

在这个注解上,又通过了@Import注解引入了一个选择器类:AutoConfigurationImportSelector。 该类实现了DeferredImportSelector接口,所以该类不仅是一个选择器,还是延迟选择器。在Spring中的ConfigurationClassParser一文中,介绍了底层Spring Framework是如何处理延迟选择器的。

选择器组类型

因为底层Spring Framework中是调用的组对象的process方法和selectImports方法,所以先来看看该选择器中使用的是哪种组类型。

v2.7.x
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
@Override
public Class<? extends Group> getImportGroup() {
    return AutoConfigurationGroup.class;
}

AutoConfigurationGroup

先来看process方法。

v2.7.x
process
selectImports
<
>
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
@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()));
    // 获取自动配置组
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(annotationMetadata);
    // 将自动配置组添加到列表中
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    // 从自动配置组对象中获取自动配置类列表并遍历,将其放到map中
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
@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));
    // 移除被排除了的自动配置类,其实在上面getAutoConfigurationEntry方法中就已经移除了,这里似乎没有意义
    processedConfigurations.removeAll(allExclusions);

    // 对自动配置类进行排序
    return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
            .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
            .collect(Collectors.toList());
}

selectImports方法中主要就是在获取process方法中准备好的自动配置类列表,然后返回就行了。重点是在process方法调用的getAutoConfigurationEntry方法中。

getAutoConfigurationEntry方法

v2.7.x
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
// annotationMetadata指的是哪个类上的注解import了该类
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);
    // 将结果封装到entry对象中
    return new AutoConfigurationEntry(configurations, exclusions);
}

这个方法中主要做了下面几件事情:

  • 加载自动配置类的全限定名并移除重复项;
  • 获取排除项并校验,如果校验成功则移除自动配置类;
  • 使用过滤器来过滤自动配置类;

加载自动配置类

v2.7.x
getCandidateConfigurations
removeDuplicates
<
>
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    /*
     * 加载classpath下面的META-INF/spring.factories文件
     * getSpringFactoriesLoaderFactoryClass()返回EnableAutoConfiguration.class
     * 就目前的版本(2.7.x)而言,spring.factories文件中不存在EnableAutoConfiguration对应的配置,
     * 而是换成了下面的AutoConfiguration.imports的方式。
     *
     * spring.factories文件这种方式已被废弃,未来版本中会被移除。
     * 而是改为了以imports结尾的文件,在classpath:/META-INF/spring目录下,相比之前多了一层spring目录。
     */
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    /*
     * 加载classpath下面的META-INF/spring/%s.imports文件���注意%s会被换成具体的类名,
     * 如下面就表示是加载META-INF/spring/AutoConfiguration.imports
     * 并把加载结果添加在configurations集合中
     */
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    /*
     * 不能为空,否则断言失败报错
     */
    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    // 返回的是一组类的全限定名称
    return configurations;
}
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
protected final <T> List<T> removeDuplicates(List<T> list) {
    // 通过set的唯一性来去重
    return new ArrayList<>(new LinkedHashSet<>(list));
}

重点是在getCandidateConfigurations方法中,在2.7.x的版本中,Spring Boot就已经没有使用传统的spring.factories文件来配置自动配置类了,而是通过”META-INF/spring/AutoConfiguration.imports“文件。所以这里只分析.imports文件是怎么读取的。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/annotation/ImportCandidates.java
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    Assert.notNull(annotation, "'annotation' must not be null");
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    // 用annotation的名称替换LOCATION字符串中的%s
    String location = String.format(LOCATION, annotation.getName());
    // 找到这些位置下的文件
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    List<String> autoConfigurations = new ArrayList<>();
    // 遍历.imports文件
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // 读取文件中的每一行,并合并到列表中
        autoConfigurations.addAll(readAutoConfigurations(url));
    }
    // 将读取到的文件内容封装到对象中
    return new ImportCandidates(autoConfigurations);
}
private static List<String> readAutoConfigurations(URL url) {
    // 读取文件
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
        List<String> autoConfigurations = new ArrayList<>();
        String line;
        // 按行读取
        while ((line = reader.readLine()) != null) {
            // 移除注释
            line = stripComment(line);
            // 移除前后的空格
            line = line.trim();
            // 忽略无效行
            if (line.isEmpty()) {
                continue;
            }
            // 添加到配置列表中
            autoConfigurations.add(line);
        }
        // 返回配置列表
        return autoConfigurations;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load autoconfigurations from location [" + url + "]", ex);
    }
}

这里对.imports文件按行读取,将每行看作是一条配置项。

排除项

v2.7.x
getExclusions
checkExcludedClasses
<
>
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    // 获取@EnableAutoConfiguration注解的exclude的属性值
    excluded.addAll(asList(attributes, "exclude"));
    // 获取@EnabelAutoConfigurtaion注解的excludeName的属性值
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    // 从environment中获取自动配置排除配置项
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}
protected List<String> getExcludeAutoConfigurationsProperty() {
    Environment environment = getEnvironment();
    if (environment == null) {
        return Collections.emptyList();
    }
    if (environment instanceof ConfigurableEnvironment) {
        Binder binder = Binder.get(environment);
        // 从environment中获取 "spring.autoconfigure.exclude“ 配置项
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
                .orElse(Collections.emptyList());
    }
    String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
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));
}

注意,不管是在@EnableAutoConfiguration注解中还是配置到environment的排除项,都需要确保被排除的类是存在的,否则Spring Boot会抛出异常。

过滤器

v2.7.x
getConfigurationClassFilter
filter
<
>
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
private ConfigurationClassFilter getConfigurationClassFilter() {
    if (this.configurationClassFilter == null) {
        List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
        for (AutoConfigurationImportFilter filter : filters) {
            invokeAwareMethods(filter); // 如果过滤器实现了一些Aware接口,那么设置对应的属性
        }
        // 将过滤器封装进入包装对象中
        this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
    }
    return this.configurationClassFilter;
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    /*
     * 2.7.x版本中有下面三个过滤器:
     * OnBeanCondition
     * OnClassCondition
     * OnWebApplicationCondition
     */
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    // 遍历过滤器
    for (AutoConfigurationImportFilter filter : this.filters) {
        // 过滤器会利用元数据判断配置类是否匹配
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) { // 所有配置类都匹配,则直接返回
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    // 处理过滤结果,只保留那些匹配上的配置类
    for (String candidate : candidates) {
        if (candidate != null) {
            result.add(candidate);
        }
    }
    return result;
}

注释中已经说明了有三个默认的过滤器,被添加到ConfigurationClassFilter对象中后,然后会在其filter方法被遍历执行。这里就不一一分析具体的过滤器了。

总结

本文分析了Spring Boot中的自动配置原理,可以发现在Spring Boot中的实现还是挺简单的,就是读取和解析各个jar包中类路径下的META-INF/spring/AutoConfiguration.imports文件,然后进行一些排除和过滤操作。真正的配置类的加载操作还是Spring Framework中的ConfigurationParer类来完成的,请参考Spring中的ConfigurationClassParser