程序员社区

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)


title: SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)
date: 2021/01/15 09:22
remark: SpringBoot 版本为 2.2.6, Spring 版本为 5.2.5


简介

SpringBoot 的自动装配与 @Configuration 注解的处理类 ConfigurationClassPostProcessor 息息相关,所以建议先看下这篇文章再继续向下看。

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图

上图中的第 576-578 行就是今天我们要将的重点:DeferredImportSelector。

继续将之前,我们先了解一下 spring.factories:

spring.factories

spring.factories 是 Spring 仿造 Java SPI 实现的一种类加载机制。它在 META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是 Spring Boot Starter 实现的基础。

spring-core 包里定义了 SpringFactoriesLoader 类,这个类实现了检索META-INF/ spring.factories 文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

  • loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。
  • loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。

上面两个方法的关键都是从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到类名列表,具体代码如下:

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.commaDelimitedListToStringArray((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);
    }
}

从代码中可以看到,在这个方法中会遍历类路径下的所有 Jar 包中的 spring.factories 文件。spring.factories 是通过 Properties 解析得到的,所以我们在写文件中的内容都是按照下面这种方式配置的,如果一个接口希望配置多个实现类,可以用","分割。

com.xxx.interface=com.xxx.classname

在日常工作中,我们可能需要实现一些SDK 或者Sring boot starter 给别人用的时候,我们就可以使用Factories机制,但是 Factories 机制可以让 SDK 或者 Stater 的使用只需要很少或者不需要进行配置,只需要在服务中引入我们的 Jar 包即可。

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图1

spring-autoconfigure-metadata.properties 文件示例:

# 配合上图中的 OnClassCondition 使用,表示引入 RabbitAutoConfiguration 配置类需要在类路径中存在 Channel 和 RabbitTemplate。
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate

为什么不直接在配置类上用 @Condition 注解来实现呢?

官方说,这样可以增加效率。

更多可以参考这篇文章:Springboot自动装配之spring-autoconfigure-metadata.properties和spring.factories

开始吧 @SpringBootApplication

本部分测试demo,虽然没啥东西。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration    // 其实就是 @Configuration,只不过把它的 proxyBeanMethods 属性的默认值改成了 false
@EnableAutoConfiguration    // 重点
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage   // 他也 Import 一个类,不过不是本文重点
@Import(AutoConfigurationImportSelector.class)  // 重点
public @interface EnableAutoConfiguration {

AutoConfigurationImportSelector

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图2

我们看到了他继承了 DeferredImportSelector,好,我们回到 @Configuration 的处理类 ConfigurationClassPostProcessor 中。

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图3

看下 deferredImportSelectorHandler#handle()

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图4

那么后面是在哪里调用的这些“延迟的” ImportSelector 的呢?

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图5

看下 deferredImportSelectorHandler#process()

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图6

tag1 handler::register

class ConfigurationClassParser {
    ...

    private class DeferredImportSelectorGroupingHandler {
        // key:组类型(在这里 AutoConfigurationGroup) value:组
        private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
        // key:配置类的注解属性 value:配置类信息(在这里是 AppBootstrap 的信息)
        private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
        //注册分组
        public void register(DeferredImportSelectorHolder deferredImport) {
            Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup(); // 这个方法有默认(default)实现,返回的是 null

            /*
            创建组
            1. 其中 createGroup(group) 就是创建了上面的 group 对象,如果为空,则创建一个默认的组对象 DefaultDeferredImportSelectorGroup。
            2. 这个方法的意思是,如果 map 中没有这个元素则用后面的方法创建,如果有则直接取出来
            */
            DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
                    (group != null ? group : deferredImport),
                    key -> new DeferredImportSelectorGrouping(createGroup(group)));
            grouping.add(deferredImport);//创建一个组,并加入DeferredImportSelectorHolder
            this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                    deferredImport.getConfigurationClass());//将注解属性和ConfigurationClass映射
        }
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图7

这里一层套一层的可能看着有点乱,看下面的这张图应该能捋清楚一点:

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图8

tag2 handler.processGroupImports()

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图9

先看下 entry 是啥吧:

class Entry {
    // 引入当前类的配置类元数据,这里就是 appBootstrap 的注解元数据
    private final AnnotationMetadata metadata;

    // 引入类的全类名,例如:org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
    private final String importClassName;
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图10
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图11

tag 到重点了 AutoConfigurationImportSelector#getAutoConfigurationEntry()

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图12
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图13
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图14
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图15
SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图16

看下 ThreadedOutcomesResolver 吧:

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图17

最后看图过一下整个的流程吧

SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)插图18
赞(0) 打赏
未经允许不得转载:IDEA激活码 » SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)

一个分享Java & Python知识的社区