title: SpringBoot 源码解析 —— 自动装配的奥秘(DeferredImportSelector)
date: 2021/01/15 09:22
remark: SpringBoot 版本为 2.2.6, Spring 版本为 5.2.5
简介
SpringBoot 的自动装配与 @Configuration 注解的处理类 ConfigurationClassPostProcessor 息息相关,所以建议先看下这篇文章再继续向下看。
上图中的第 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 包即可。
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
我们看到了他继承了 DeferredImportSelector,好,我们回到 @Configuration 的处理类 ConfigurationClassPostProcessor 中。
看下 deferredImportSelectorHandler#handle()
那么后面是在哪里调用的这些“延迟的” ImportSelector 的呢?
看下 deferredImportSelectorHandler#process()
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映射
}
这里一层套一层的可能看着有点乱,看下面的这张图应该能捋清楚一点:
tag2 handler.processGroupImports()
先看下 entry 是啥吧:
class Entry {
// 引入当前类的配置类元数据,这里就是 appBootstrap 的注解元数据
private final AnnotationMetadata metadata;
// 引入类的全类名,例如:org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
private final String importClassName;
tag 到重点了 AutoConfigurationImportSelector#getAutoConfigurationEntry()
看下 ThreadedOutcomesResolver 吧: