Spring 自动装配【11】 import - 解析 前

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

前言

我们知道:

  • SpringBoot简化了Spring的配置,有了SpringBoot之后我们就不用写一大堆东西来引入中间件了,只需要我们在启动类上加上:
1
java复制代码@SpringBootApplication

并加上对应的中间件的注解,比如此时我们需要引入一个Eureka:

1
css复制代码@EnableEurekaClient

这样我们就能把对应的中间件引入到应用中了(当然,其他的什么地址之类的东西还是要配置的)。

那么这个功能是如何实现的呢?

@SpringBootApplication

首先,我们来看看这个注解:

1
2
3
4
5
6
7
8
9
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

自动配置自动配置,这里有个注解和我们想看到的关系很大:

@EnableAutoConfiguration

这个注解如下:

1
2
3
4
5
6
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

这里两个auto,我们先看看这个:

1
2
3
4
5
6
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

到这里出现了两个import,这是干啥用的?看起来应该是很重要的东西。

那么来看看这个Import注解:

1
2
3
4
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

注解上写的是:标识一个或多个component类需要被引入,例如:@Configuration标识的类。

@Configuration注解可太熟了,注册数据源、redis之类的东西,都会写这么个玩意。

打开idea看看用到@Import注解的地方,可以发现其实大部分的enableXXX的注解,都是通过这玩意实现的,例如:

1
2
3
4
5
java复制代码@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

那么此时就有问题了:

  • 这个@Import是如何工作的?
  • @Import中的类,又该如何编码呢?

@Import

继续通过idea,来看看@Import是在什么地方被处理的.

找了好久终于找到了两个类对于这个注解有处理:

  • ConfigurationClassParser
  • ConfigurationClassUtils

我们一个个看。

ConfigurationClassParser

收集import信息

我们是从这段代码中看到Import注解被处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {

if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}

方法上的注解写得很明白:

  • 递归地将Import的值收集起来。
  • 至于为什么是递归,下面也给了个例子:
1
2
3
java复制代码For example, it is common for a @Configuration class to declare direct
@Import s in addition to meta-imports originating from an @Enable
annotation.

​ 意思大概就是**@Configuration经常用一个@Import注解,来引入一些源于@Enable**注解的元导入。

​ 也就是说一层是不够解析出要import的所有对象的,写代码的经常除了@Import之外给你用一些@EnableXXX的玩意,这些玩意在这里也不好直接解析(万一enable里再enable的情况咋整),所以这里就直接给开个递归解决这种问题。

这个方法粗浅一点看也大概知道是个什么意思:将sourceClass里包括的(以及可能存在的更多层次的)@Import里标识的那些类,给你塞到这个imports里。

这里的sourceClass不必深究,我们此时看到的代码层次只要知道是对于class对象的一个封装即可。【1】

上面方法的链路

上面的方法我们也看到了:

  • 这其实就是个收集的方法

那么这里离我们要看到的如何注入的东西还是相差甚远的,此时我们在这个子问题中需要解决的是:

  • 哪里调用来收集?
  • 哪里处理已收集的信息?

我们先来看看这个方法的调用链:

doProcessConfigurationClassgetImportscollectImportsprocessImports
getImport就是个safe的代理方法,本身没什么就是保证两个list不是null。

上面的关系是从这里得出的:

1
2
java复制代码// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

看样子,processImports就是处理这些import注解的方法了。

import信息的处理

这里一共五十几行代码,为了方便对应啥意思就写代码上了,可能会出现阅读困难的地方做了标记,下面会解释:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
java复制代码private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {

//如果你要import的东西都没有,那我就不处理了
if (importCandidates.isEmpty()) {
return;
}

//这里就是判断是否有循环import的说法了
//我们知道Spring会给你处理循环的bean,但是这里如果出现循环就直接报错了
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}

//其实这里都不用写else,上面的卫语句已经处理掉了
else {
//上面判断是不是有循环的stack,在这里做更新
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//如果我们import的类是个ImportSelector:实例化这个selector【A1】
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
//这里就是加上了selector中指定的判断了
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
//看名字也知道:延迟importSelector,把这个行为往后延迟,等到后面再处理【A2】
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
//这里对应的是:是importSelector,但不是延迟的,那么就直接处理这个importSelector了呗
else {
//调用selectIimports方法,获取需要import的类名称
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//封装
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
//递归了,原因跟上面的collectImports类似
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
//这里的else对应的是:sourceClass不是ImportSelector,而是:ImportBeanDefinitionRegistrar【A3】
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
//这里看注释就知道是来处理@Configuration的【2】
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
  • 【A1】:ImportSelector

还记不记得之前提到的EnableAutoConfiguration?这里import的就是个ImportSelector:

@Import(AutoConfigurationImportSelector.class)

这个接口提供了两个方法:

1
2
3
4
5
6
7
8
9
> javascript复制代码//根据元数据,返回需要导入的具体的class名称
> String[] selectImports(AnnotationMetadata importingClassMetadata);
>
> //返回一个判断函数,这个函数如果返回的是true,代表输入的String不会被作为一个配置类处理
> default Predicate<String> getExclusionFilter() {
> return null;
> }
>
>
  • 【A2】:DeferredImportSelector

从类名可以看出来:延迟的importSelector,当然是继承ImportSelector的。

这个接口额外提供了一个方法:

1
2
3
> scss复制代码Class<? extends Group> getImportGroup()
>
>

这个ImportGroup,是这个接口中定义的一个子类,用于收集不同importSelector中的结果的。

上面说的AutoConfigurationImportSelector实际上实现的是这个接口。

而这里的handle其实做的事情就是把这个延迟的给存起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> 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);
> }
> }
>
>
  • 【A3】:ImportBeanDefinitionRegistrar

看类名,这个就和BeanDefinition相关了,该接口上的注释也是这么说的:

1
2
3
4
5
> > java复制代码Interface to be implemented by types that register additional bean definitions when
> > processing @{@link Configuration} classes. Useful when operating at the bean definition
> > level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
> >
> >

大概就是说这个类,是用来注册一些额外的beanDefinition的,这部分我们放到后面再来看看。【3】

小结

那么,上面的代码做了什么事情?

  • 通过预先取出来import里的SourceClass来做处理。
  • 如何处理(下面说的是sourceClass对应的类)?
+ 如果是**importSelector**:
    - 如果不是:递归往下处理
    - 如果是延迟(DeferedImportSelector):
        * 那么先存起来
+ 如果不是,分为**ImportBeanDefinitionRegistrar**和**其他的情况**,分别处理【2】【3】这里做一下分析:


+ 因为处理必须是**有目的**的:要么改变某些东西,要么保存某些东西。
+ 而这里如果是ImportSelector,且不是Defered,那么其实是**递归处理的**,**没有做额外的保存**等工作
+ 因此,因为这里的处理有目的性,可见**ImportSelector**中的**selectImport**方法,返回的**最终结果**(多次递归之后最终不需要再往下递归的情况),**在这里**必须是**DeferImportSelector**或者是**ImportBeanDefinitionRegistrar**,或者是其他的东西(多是@Configuration,反正是可以按照@Configuration处理的),而不能只是ImportSelector。因此这里回过头来看【A1】,是否对于**selectImports**方法有了更深一点的理解?

小结

这里我们算是知道了:

  • @import注解是在哪个方法中做的处理。

但就现在来看,我们只知道这个注解可能:

  • 有多层次的情况
  • 有循环import的可能

以及一些具体处理的细节。

但是具体处理的落地,在挖的坑【2】和【3】中。

这就意味着此时我们并没有看完整个过程的代码,只是流程中的一小部分,此时还涉及到:

  • 这个方法在哪里被调用? - 这个事情很关键,我们需要知道@Import是如何工作的。
  • 下面的坑要填 - 【2】【3】具体如何落地?
  • 延迟的ImportSelector是如何工作的?

还有【1】,这个SourceClass是什么东西?

本文转载自: 掘金

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

0%