第六节 自动装配源码理解

727 阅读7分钟

tips: 不同版本代码实现有差异。

前面两章了解的流程,就是 SpringBoot 自动转配的核心。

一、自动装配

1.1 什么是 SpringBoot 自动装配?

自动装配是 Spring 框架用来减少配置的显式需求而引入的一个特性,该特性通过 @Autowired或者@Resource注解实现依赖对象的自动注入。而 Spring Boot 在此基础上进一步发展,提出了更高级的自动配置(Auto-configuration)概念。SpringBoot 在启动时会扫描外部 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装配到 Spring 容器里面。

自动装配机制是Spring Boot提供的核心特性之一,它极大地提高了开发效率,同时确保了配置的简洁性和灵活性,实现“约定大于配置”

1.2 自动装配的工作原理

自动装配,抓住三个要素。(在前面章节我们已经分析了其核心流程)

  1. @EnableAutoConfiguration

  2. AutoConfigurationImportSelector

  3. spring.factories

AutoConfigurationImportSelector是自动装配背后的关键角色。实现自ImportSelector接口,它会从 META-INF/spring.factories 文件中加载EnableAutoConfiguration指定的配置类。这是通过SpringFactoriesLoader类来实现的,它为Spring Boot自动装配提供了加载和解析spring.factories文件的能力。

spring.factories: 位于META-INF目录下的spring.factories文件包含了自动配置类的全限定名列表。当应用启动时,这些配置类会被加载并根据条件判断是否应用到当前的应用上下文中。

@EnableAutoConfiguration 作为自动装配的关键。

二、从注解入手

以 mybatis-starter-apply 为例子开始

gitee地址:gitee.com/uzongn/uzon…

在我们启动类上有这样的注解 SpringBootApplication,它是开启配置自动装配的大门钥匙。

@SpringBootApplication

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

分析 @SpringBootApplication 注解, 它是多个注解的集成。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}
  • @Configuration: SpringBootConfiguration 等于 Configuration。属于派生。

  • @EnableAutoConfiguration: 负责激活 SpringBoot 自动装配机制

  • @ComponentScan: 路径扫描,该扫描路径下的 bean 都会被扫描加载,另外可以排除不需要的类。 即激活 @Conponent 的扫描

@SpringBootApplication 等价于上面三个注解。而实现自动装配的关键注解是 @EnableAutoConfiguration

@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
  • @Import 导入 AutoConfigurationImportSelector 配置类,它会加载各种自动化配置类, 它是实现 Starter 的核心
  • @AutoConfigurationPackag: 由AutoConfigurationPackages.Registrar(一个实现了ImportBeanDefinitionRegistrar的类)处理。默认值是启动类所在的包路径,默认指定启动类路径下的类加载到 Spring 容器。主要逻辑在 AutoConfigurationPackages#register 方法中。 该方法有两个参数 registry 和 packageNames。packageNames 的值默认是启动类包所在的路径。如下所示:

对应的栈帧

registry 即 DefaultListableBeanFactory

Lite && Full (拓展理解)

@Bean 的声明方式为“轻量模式 Lite”

@Configuration 下声明的@Bean为“完全模式Full”,存在 cglib 提升

三、AutoConfigurationImportSelector

关于 AutoConfigurationImportSelector, 它实现了 ImportSelector 接口(3.x 开始)

3.1 ImportSelector

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

ImportSelector 用于条件性的导入配置类。是Spring框架处理@Import注解的一部分,提供了一种灵活的方式来根据运行时的条件动态决定 Spring 配置; 尤其是在开发自定义自动配置和框架扩展时。

selectImports方法的返回值是一个包含配置类全路径名称(Fully qualified names)的字符串数组

ImportSelector可以与注解一起使用,让开发者在@Configuration注解的类上通过@Import来导入它。它不直接将配置类加入到Spring上下文中,而是提供一种选择性地导入配置类的方式。

通过ImportSelector,开发者可以在运行时根据条件(比如classpath中是否存在某个类,或者某个属性是否被定义等)来决定是否导入某些配置。这提供了极大的灵活性,使得 Spring Boot 的自动配置成为可能。

开发一个可插拔的模块或框架时,ImportSelector可以用来根据应用的配置或依赖来动态导入配置类。Spring Boot 大量使用了ImportSelector来实现自动配置。通过条件检查,只有当满足特定条件时,如果类路径中有特定的类存在,或某些属性被定义,相关的配置类才会被导入

3.2 DeferredImportSelector

/**
 * A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
 * have been processed. This type of selector can be particularly useful when the selected
 * imports are {@code @Conditional}.
 *
 * <p>Implementations can also extend the {@link org.springframework.core.Ordered}
 * interface or use the {@link org.springframework.core.annotation.Order} annotation to
 * indicate a precedence against other {@link DeferredImportSelector}s.
 *
 * @author Phillip Webb
 * @since 4.0
 */
public interface DeferredImportSelector extends ImportSelector {

}

A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans

have been processed

DeferredImportSelectorImportSelector的一个扩展接口,它在常规的ImportSelector执行之后稍晚运行。这允许其他的配置类先被处理,使得DeferredImportSelector可以在做决定时考虑到这些先前的配置。

而 AutoConfigurationImportSelector 就是实现了 DeferredImportSelector 该接口。

ImportSelector是Spring框架中一个强大的特性,它为条件化配置和自动配置提供了强大的支撑,是实现灵活、动态导入Spring配置的关键机制。通过ImportSelector和它的扩展,Spring实现了一套强大的配置和自动配置机制,极大地简化了Spring应用的配置工作。

3.3 AutoConfigurationImportSelector

核心方法 selectImports

自动配置机制能够有条件地导入合适的配置,这对整合第三方库、开发自定义 starter 或在运行时动态调整配置都是至关重要的

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {

    // 检查自动配置是否启用,如果没有就直接返回一个空的导入数组。
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }

    try {
        // 加载自动配置元数据,可能包括配置类的属性、条件以及其他相关信息。
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);

        // 取得 @EnableAutoConfiguration 注解的属性,这可能含有排除某些配置类的信息。
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        // 获取所有候选的自动配置类列表。
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);

        // 移除配置类列表中的重复元素。
        configurations = removeDuplicates(configurations);

        // 基于AutoConfigurationMetadata将配置类列表排序。
        configurations = sort(configurations, autoConfigurationMetadata);

        // 获取所有排除的配置类。
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);

        // 校验排除的类是否真正存在于候选配置中,并抛出异常如果有必要。
        checkExcludedClasses(configurations, exclusions);

        // 从候选列表中移除所有排除的类。
        configurations.removeAll(exclusions);

        // 过滤掉那些不应被导入的配置类。
        configurations = filter(configurations, autoConfigurationMetadata);

        // 触发一系列自动配置导入事件。
        fireAutoConfigurationImportEvents(configurations, exclusions);

        // 将配置类列表转换为字符串数组返回。
        return StringUtils.toStringArray(configurations);
    }
   ......
}

3.3 isEnabled

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

首先调用 isEnabled 方法去判断自动化配置到底有没有开启,可以通过在 application.properties 中配置 spring.boot.enableautoconfiguration=false 来关闭所有的自动化配置。

3.4 getCandidateConfigurations

获取 claspath:META-INF/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;
}

注意:getSpringFactoriesLoaderFactoryClass(), 返回的是 EnableAutoConfiguration.class。

到这里,我们应该知道 spring.factories 的前缀,为什么要定义成org.springframework.boot.autoconfigure.EnableAutoConfiguration 这种固定key了吧。

3.5 removeDuplicates

除候选自动化配置类中重复的类。借助 LinkedHashSet 去除重复

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

3.6 getExclusions

	protected Set<String> getExclusions(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		Set<String> excluded = new LinkedHashSet<>();
		excluded.addAll(asList(attributes, "exclude"));
		excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
		excluded.addAll(getExcludeAutoConfigurationsProperty());
		return excluded;
	}

getExclusions 获取到所有被排除的自动化配置类。

  1. 当前注解的 exclude 属性获取
  2. 当前注解的 excludeName 属性获取
  3. getExcludeAutoConfigurationsProperty() 从配置文件中的 spring.autoconfigure.exclude 属性获取

3.7 checkExcludedClasses

校验不是 not auto-configuration 类则不能使用 exclude 方式做类的排除。

3.8 filter

加载完所有的自动化配置类了,但是,这些配置类是否生效,还需要根据当前项目的依赖等进行加载生效。

比如:MybatisAutoConfiguration 是否能够加载,需要查看当前的依赖是否存在,比如 SqlSessionFactory.class

for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    invokeAwareMethods(filter);
    boolean[] match = filter.match(candidates, autoConfigurationMetadata);
    for (int i = 0; i < match.length; i++) {
        if (!match[i]) {
            skip[i] = true;
            skipped = true;
        }
    }
}

处理下面两个注解

  1. ConditionalOnClass
  2. ConditionalOnMissingClass

3.9 其他方法

fireAutoConfigurationImportEvents:于那些想要在导入配置之前进行特定操作的扩展点。

3.10 小结

到这里,关于 SpringBoot 的Starter 能够进行扩展,来源于 ImportSelector 实现类。 它是能够实现扩展的核心。AutoConfigurationImportSelector 是 SpringBoot 中的类, 但是 ImportSelector 是springframework core 中的类。

这些 Configuration 类,将使用 ConfigurationClassParse 进行解析。

四、 ConfigurationClassParse

processDeferredImportSelectors 导入配置类。

private void processDeferredImportSelectors() {

    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
        ConfigurationClass configClass = deferredImport.getConfigurationClass();
        try {
            String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
            processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
        }
        ......
    }
}

五、本章小结

本章能够实现扩展,是来源于 ImportSelector(DeferredImportSelector) 接口。AutoConfigurationImportSelector(SpringBoot) 整好扩展这个类,使得 Starter 模式能够非常优雅的进行扩展。

那么下一章,我们将进一步扩展理解 ConfigurationClassParse 这部分源码。