springboot 自动装配之加载自动装配类

2,569 阅读10分钟

注:本系列源码分析基于springboot 2.2.2.RELEASE,对应的spring版本为5.2.2.RELEASE,源码的gitee仓库仓库地址:funcy/spring-boot.

自动装配是springboot的核心之一,本文将来探究springboot是如何加载自动装配类的。

@SpringBootApplication 注解一文中,我们提到springboot处理自动装配的注解是@EnableAutoConfiguration,代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动装配的包
@AutoConfigurationPackage
// 引入的自动装配类
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * 可自行定义排除自动装配的类
     */
    Class<?>[] exclude() default {};

    /**
     * 可自行定义排除自动装配的类名
     */
    String[] excludeName() default {};

}

以上代码包含三个部分:

  1. @AutoConfigurationPackage:指定自动装配的包;
  2. @Import(AutoConfigurationImportSelector.class):引入自动装配的处理类AutoConfigurationImportSelector,这个类是自动装配的关键所在;
  3. @EnableAutoConfiguration 的属性:@EnableAutoConfiguration提供了两个属性:excludeexcludeName,可以用来排除不需要自动装配的类。

本文重点来分析AutoConfigurationImportSelector类。

1. AutoConfigurationImportSelector.AutoConfigurationGroup

AutoConfigurationImportSelector 实现了 DeferredImportSelector,关于DeferredImportSelector的分析,可以参考ConfigurationClassPostProcessor之处理@Import注解,这里我们直接给出结论:

  • DeferredImportSelectorImportSelector 的子接口,其内部有一个接口Group,该接口定义了两个方法:

    public interface DeferredImportSelector extends ImportSelector {
        ...
    
        interface Group {
    
            /**
             * 处理导入操作
             */
            void process(AnnotationMetadata metadata, DeferredImportSelector selector);
    
            /**
             * 返回导入类
             */
            Iterable<Entry> selectImports()
        }
    }
    

    在处理DeferredImportSelector的导入类时,DeferredImportSelector.Group#process方法会先调用,然后再调用DeferredImportSelector.Group#selectImports返回导入类;

  • DeferredImportSelector 可以指定导入类的分组,在处理时,可以按分组处理导入类;

  • DeferredImportSelector 在处理导入类时,先将导入类按分组放入一个map中,在处理完其他配置类(spring的配置类为@Component@ComponentScan@Import@Configuration@Bean 标记的类)后再来处理分组中的导入类,也就是说,DeferredImportSelector导入的类,会在其他类注册到beanFactory中后,再进行注册(注册前还需判断能否注册到beanFactory,若能才注册)。

我们来看看AutoConfigurationImportSelector的代码:

// 实现了 DeferredImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, 
        BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    ...

    /**
     * 这里实现了 DeferredImportSelector.Group
     */
    private static class AutoConfigurationGroup implements DeferredImportSelector.Group, 
            BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

        /**
         * 保存导入的类
         */
        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()));
            // 1. 调用 AutoConfigurationImportSelector#getAutoConfigurationEntry(...) 方法,
            // 在这个方法里会加载自动装配类
            AutoConfigurationEntry autoConfigurationEntry = 
                ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            // 2. 将获取到的 autoConfigurationEntry 保存起来
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

        /**
         * 返回导入类
         */
        @Override
        public Iterable<Entry> selectImports() {
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            }
            // 3. 得到过滤类
            Set<String> allExclusions = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream)
                    .collect(Collectors.toSet());
            // 4. 将 autoConfigurationEntries 转换为 LinkedHashSet
            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            // 5. 去除需要过滤的类
            processedConfigurations.removeAll(allExclusions);
            // 6. 进行排序
            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata())
                    .stream().map((importClassName) -> new Entry(
                        this.entries.get(importClassName), importClassName))
                    .collect(Collectors.toList());
        }

        ...
    }


}

这里我们将DeferredImportSelector.Group#processDeferredImportSelector.Group#selectImports两个方法结合起来看,处理步骤总结如下:

  1. 调用 AutoConfigurationImportSelector#getAutoConfigurationEntry(...) 方法加载自动装配类;
  2. 将得到的自动装配类保存到autoConfigurationEntries中;
  3. 得到过滤类,这些过滤类就是由@EnableAutoConfigurationexcludeexcludeName指定的;
  4. autoConfigurationEntries 转换为 LinkedHashSet,结果为processedConfigurations
  5. 去除processedConfigurations需要过滤的类;
  6. 将第5步得到的类排序后,返回。

接下来我们对这些关键步骤进行分析。

特别说明:DeferredImportSelectorImportSelector 的子接口,ImportSelector 处理导入类的方法是 selectImports(...),在DeferredImportSelector中也重写了该方法:

这个方法所做的也是加载自动装配类,返回最终导入的类,但需要注意的是,springboot 的自动导入类不是在这里处理的,关于这点,可以在方法内打个断点,然后就会发现这个方法并没有运行到!

最后再声明下:springboot 的自动导入类不是AutoConfigurationImportSelector#selectImports 方法中处理的,而是在AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports方法中处理的。

2. 获取装配类:AutoConfigurationImportSelector#getAutoConfigurationEntry

自动配置 类的加载代码为:

AutoConfigurationEntry autoConfigurationEntry = 
    ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

该代码就是用来加载自动装配类的,我们直接进入AutoConfigurationImportSelector#getAutoConfigurationEntry方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 又一次判断是否开启自动装配
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获取注解的属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 1. 加载候选的自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 2. 去重,转换成set,再转换成list
    configurations = removeDuplicates(configurations);
    // 3. 去除需要排除的类,其实就是处理@EnableAutoConfiguration的exclude与excludeName
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 4. 过滤不需要自动装配的类
    configurations = filter(configurations, autoConfigurationMetadata);
    // 5. 触发 AutoConfigurationImportEvent 事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 6. 最终返回的值
    return new AutoConfigurationEntry(configurations, exclusions);
}

这个方法非常重要,包含了获取自动装配类的全部操作,该操作流程如下:

  1. 加载候选的自动装配类,springboot 自动装配的类位于classpath下的 META-INF/spring.factories文件中,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这个我们后面再详细分析;

  2. 去除重复的自动装配类,上一步加载得到的自动装配类可能会有重复,在这里会去除重复的类,去除方式也非常简单,springboot就只是先转换成Set,再转换成List

  3. 去除排除的类,前面提到@EnableAutoConfiguration可以通过excludeexcludeName指定需要排除的类,这一步就是来处理这两个属性的;

  4. 过滤不需要自动装配的类,根据本人调试,发现并没有完成过滤:

    过滤前是124个:

    过滤后还是124个:

  5. 触发 AutoConfigurationImportEvent 事件;

  6. 将第3步得到的排除类与第4步得到的自动装配类包装成AutoConfigurationEntry返回。

注意最后一行代码:

// 6. 最终返回的值
return new AutoConfigurationEntry(configurations, exclusions);

这里把configurationsexclusions 都传入了AutoConfigurationEntry的构造方法,我们来看看AutoConfigurationEntry

protected static class AutoConfigurationEntry {
    // 自动装配类
    private final List<String> configurations;

    // 需要排除的自动装配类
    private final Set<String> exclusions;

    /**
     * 构造方法,对再者进行赋值
     */
    AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
        this.configurations = new ArrayList<>(configurations);
        this.exclusions = new HashSet<>(exclusions);
    }

    ...
}

由些可见,最终返回的AutoConfigurationEntry包含两大内容:

  • configurations:自动装配类,已经去除了需要排除的类
  • exclusions:通过@EnableAutoConfiguration 指定的需要排除的类

整个自动装配类的获取就是这样了,下面我们来看看加载候选的自动装配类的流程。

3. 加载候选的自动装配类

自动装配类的加载位于AutoConfigurationImportSelector#getCandidateConfigurations,代码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 
        AnnotationAttributes attributes) {
    // 调用的是 spring 提供的方法:SpringFactoriesLoader.loadFactoryNames(...)
    // getSpringFactoriesLoaderFactoryClass() 返回的是EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader
            .loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations, "...");
    return configurations;
}

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

继续进入SpringFactoriesLoader#loadFactoryNames


public final class SpringFactoriesLoader {

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        // 得到的 factoryTypeName 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    /**
     * 在这里进行加载,加载的是 META-INF/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 {
            // 加载 META-INF/spring.factories 的内容
            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);
                // 将 META-INF/spring.factories 的内容转换为 Properties 对象
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    // StringUtils.commaDelimitedListToStringArray(...) 逗号分割为数组
                    for (String factoryImplementationName : 
                                StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
    ...
}

可以看到,这里加载的是classpath下的META-INF/spring.factories文件,注意:这个文件可能会有多个,位于不同的jar包中。

springboot自带的META-INF/spring.factories 位于 spring-boot-autoconfigure模块下:

我们来看一眼spring.factories

这个文件定义了许多的配置类,以key-value的形式保存,多个值之间使用“,”分开,上面提到的自动装配类的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,对应的value非常多,这里就不展示了。

这一步之后,自动装配类就被注册到spring容器中了。注:此时加载到spring容器中的还是BeanDefinition,要想成为 spring bean,还得经过ConditionalOnBeanConditionalOnClass等注解的考验,这些我们后面再分析。

4. 获取自动装配类后的处理

让我们再回到AutoConfigurationImportSelector.AutoConfigurationGroup,在第1节我们总结的流程如下:

  1. 调用 AutoConfigurationImportSelector#getAutoConfigurationEntry(...) 方法加载自动装配类;
  2. 将得到的自动装配类保存到autoConfigurationEntries中;
  3. 得到过滤类,这些过滤类就是由@EnableAutoConfigurationexcludeexcludeName指定的;
  4. autoConfigurationEntries 转换为 LinkedHashSet,结果为processedConfigurations
  5. 去除processedConfigurations需要过滤的类;
  6. 将第5步得到的类排序后,返回。

以上第2节与第3节,分析的是自动加载类的加载过程,我们再来看看接下来的步骤。

对照着代码,我们会发现接下来的步骤都比较简单,这里也逐一说明下吧。

  • 第2步,保存得到的自动装配类,这个操作仅仅只是调用了List#add(...)方法,将得到的autoConfigurationEntry保存到autoConfigurationEntries,这个结构是AutoConfigurationGroup的成员变量,在AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports 方法中会用到;

  • 第3步,得到的是所有的过滤类,该过滤类是遍历autoConfigurationEntries,然后通过autoConfigurationEntry#getExclusions 方法得到的 ,前面我们也提到过,autoConfigurationEntry 只包含两个成员变量:configurations(去除排除类后的自动装配类) 与 exclusions(通过@EnableAutoConfiguration指定的排除类);

  • 第4步,将List转换为LinkedHashSet,不分析;

  • 第5步,对所有的自动装配类再进行一次去除排除类的操作,排除的对象是所有的排除类,这种情况应该是会对同一项目中有多个@EnableAutoConfiguration的情况,比如第一个@EnableAutoConfiguration注解排除AB两个类,第二个@EnableAutoConfiguration注解排除CD两个类,那最终排除的是ABCD四个类;

  • 第6步,这一步的主要操作是排序,这个顺序决定了自动装配类注册到beanFactory中的顺序,AutoConfigureOrder@AutoConfigureAfter@AutoConfigureBefore 就是在这里处理的,关于这块内容,可以参考springboot 自动装配之自动装配顺序.

经过这些步骤后,自动装配的获取就完成了。

5. 自定义自动装配类

了解完自动装配类的加载过程后,我们也可以自定义一个自动装配类。

  1. 准备一个自动装配类
@Configuration
public class MyAutoConfiguration {

    @Bean
    public Object object() {
        System.out.println("create object");
        return new Object();
    }
}

这个类很简单,就是一个标记了@Configuration的类,类中使用@Bean注解创建了一个bean,在创建bean的过程中会 打印"create object"。

  1. 准备META-INF/spring.factories 内容如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.learn.autoconfigure.demo01.configure.MyAutoConfiguration
  1. 主类
@SpringBootApplication
public class AutoconfigureDemo01Application {

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

}

运行结果如下:

可以看到,create object成功打印了。

那这个bean是通过包扫描创建的,还是自动装配导入的呢?我们通过调试的方式来看下自动装配得到的类:

可以看到,MyAutoConfiguration就在自动装配类的列表中了。

注意到,MyAutoConfiguration 加了 @Configuration注解, 那么它究竟是由sping容器扫描到的,还是由自动装配得到的呢?

【springboot源码分析】@SpringBootApplication 注解一文中,我们提到SpringBootApplication 注解中的@ComponentScan 会指定一个过滤器:AutoConfigurationExcludeFilter,这个过滤器会过滤自动装配类,这里我们看下目前为止beanFactory都有哪些beanName

可以看到,并没有MyAutoConfiguration,因此此时它还没被扫描进beanFactory中。

当然,我们也可以把MyAutoConfiguration上面的@Configuration注解去掉,这样就不会有这个困惑了。

6. 总结

本文从@EnableAutoConfiguration注解出发,分析了自动装配类的加载流程,加载流程在AutoConfigurationImportSelector#getAutoConfigurationEntry方法中,最终加载的是META-INF/spring.factories文件中key是org.springframework.boot.autoconfigure.EnableAutoConfiguration的类。

得到自动装配类后,spring会将其注册到容器中,此时它们还是一个的BeanDefinition,要想成为 spring bean,还得经过ConditionalOnBeanConditionalOnClass等注解的考验,这些我们后面再分析。


本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

【springboot源码分析】springboot源码分析系列文章汇总