Spring 自动装配【12】processImport中

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

前言

上一篇文章中我们通过idea引用查找的功能,找到分析了:

  • @Import注解是在哪里被处理的

这里我们接着往下,来看看@Import最终具体执行的三种情况:

  • DeferImportSelector
  • ImportBeanDefinitionRegistrar
  • 可以按照**@Configuration**处理的

这些情况下分别是如何处理的。

DeferImportSelector

上一篇文章里我们知道了:

  • DeferImportSelector在processImport方法中的处理方式,暂时认为只是暂时保存到deferredImportSelectors这个list中保存起来了。

那么,这些保存起来的信息具体是在哪里使用的?

处理

这个类中有一个process方法,那么就应该是处理的方法了,我们来看看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
java复制代码public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
//这里的register其实只是把数据往里面塞的动作
deferredImports.forEach(handler::register);
//看下面
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}

public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
//是不是看到了我们的老朋友?
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}

这里就看到,又调到processImports里了。

之前的handler的方法我们没有细究,这里再贴一下代码,这样子运转机制就清晰了:

1
2
3
4
5
6
7
8
9
10
11
12
13
> java复制代码public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
> DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
> if (this.deferredImportSelectors == null) {
> DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
> handler.register(holder);
> handler.processGroupImports();
> }
> else {
> this.deferredImportSelectors.add(holder);
> }
> }
>
>

其实这里的processGroupImport,在处理import注解的Defer的时候已经调用了(processImport),那么此处综合起来看就是

  • 处理的时候,每当有一个新的Defer进来,先处理之前的存的,再把这个defer的存起来。
  • 最后调到这个process的时候,就会把所有的group执行一下,并把这些收集的group回收掉。

这个关系如下:

processImport新的defer来了如果容器为空注册holder并执行如果容器不为空存起来process将容器置为null调用processGroupImport注册所有holder并执行新建容器
而这个容器(deferredImportSelectors),只在process方法的时候会置空,处理完之后就变成了新的容器(new)。

这说明:

  • 这里的defer延迟,意即:
    • 如果process方法不在执行中时,这些importSelector会暂时保存起来。
    • 如果process方法在执行并且已经开始了(标志是:容器为空),那么此时不能偷懒往后延迟了,只能自己来执行。

DeferredImportSelectorHandler的handle方法上的注解很明确地说了这一点:

1
2
3
4
5
6
> java复制代码Handle the specified {@link DeferredImportSelector}. If deferred import
> selectors are being collected, this registers this instance to the list. If
> they are being processed, the {@link DeferredImportSelector} is also processed
> immediately according to its {@link DeferredImportSelector.Group}.
>
>

调用

代码中我们可以发现:

  • 只有通过handler,才能调用和保存这些DeferredImportSelector

而这个类只在ConfigurationClassParser.parseprocessImports中被调用:

  • parse调用process方法
  • processimport调用handle方法

小结

这里也能看出来,DeferredImportSelector也并没有实际上将import落地处理掉,处理的时候也是得委托到下面两个来。

ImportBeanDefinitionRegistrar

在processImport中,这部分的处理其实也比较简单:

1
2
3
4
5
java复制代码Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());

实际上,就是实例化该类,并放到configClass中。那么这部分最终在哪里处理呢?

借助idea可以看到在这里被用到了(类:ConfigurationClassBeanDefinitionReader):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码//这个方法在loadBeanDefinitions被循环调用	
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//........
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

//一般来说,这里的registry实际上是在Spring启动中的容器(各种context)
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

//而上面的这里:
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {

registerBeanDefinitions(importingClassMetadata, registry);
}

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//子类实现
}

看到registerBeanDefinitions,这里就比较清楚了:

  • 解析到的ImportBeanDefinitionRegistrar,最终会被丢到context中来初始化成beanDefinition。

这个接口的注解也说明了这一点:

1
2
java复制代码Register bean definitions as necessary based on the given annotation metadata of
the importing {@code @Configuration} class.

如果要真的看明白这个方法是如何实现的,那么就需要trace到上游调用和子类实现了,因此我们放到后面再说。【ImportBeanDefinitionRegistrar

@Configuration方式

这里的处理在这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
java复制代码processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}

ConfigurationClass existingClass = this.configurationClasses.get(configClass);
//如果这里已经保存了
if (existingClass != null) {
//并且想放进去的configClass,importedBy容器中不是空的
if (configClass.isImported()) {
//原来已有的也是通过@Import注册的(importedBy容器中不是空的)
if (existingClass.isImported()) {
//我们把原来的合并一下
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
//这里的else对应的是上面的configClass.isImported(),意思就是:
//我们期望放进来的,不是通过import注解的
//看下面的注释,意思就是:把原来的记录给删掉,已现在的为准
//但是我们是通过@Import注解进来的,这个是咋会不是import的呢?
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}

//递归地构建这个sourceClass
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
//这里是processImport调用的一个地方,在前面我们提到过,到这里就变成递归调用了
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);

this.configurationClasses.put(configClass, configClass);
}

这里也能看到:

  • 如果是@Configuration,其实最终还是得变成ImportBeanDefinitionRegistrar,否则就继续递归了。

小结

看完这部分,应该对processImport中的四种方式有了一个大概了解:

  • 如果是要最终解析的,那么八成是得通过ImportBeanDefinitionRegistrar来导入了,否则就是一直递归到最下面,变成ImportBeanDefinitionRegistrar
  • 这里埋得坑:
+ 类:【**ImportBeanDefinitionRegistrar**】
    - 何处处理的?
    - 子类如何实现的?
    - configurationClass类中,**loadBeanDefinitionsForConfigurationClass**在哪里,何时被调用?
+ 方法:**processConfigurationClass**
    - 这个方法中为什么会有**ConfigurationClass**不是import的情况出现呢,也就是说:
        * ConfigurationClass中的importedBy是如何维护的?
+ 还有之前没有处理的问题:
    - **doProcessConfigurationClass**干了啥?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%