这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」
前言
我们知道:
- SpringBoot简化了Spring的配置,有了SpringBoot之后我们就不用写一大堆东西来引入中间件了,只需要我们在启动类上加上:
1 | java复制代码@SpringBootApplication |
并加上对应的中间件的注解,比如此时我们需要引入一个Eureka:
1 | css复制代码@EnableEurekaClient |
这样我们就能把对应的中间件引入到应用中了(当然,其他的什么地址之类的东西还是要配置的)。
那么这个功能是如何实现的呢?
@SpringBootApplication
首先,我们来看看这个注解:
1 | java复制代码@Target(ElementType.TYPE) |
自动配置自动配置,这里有个注解和我们想看到的关系很大:
@EnableAutoConfiguration
这个注解如下:
1 | java复制代码@Target(ElementType.TYPE) |
这里两个auto,我们先看看这个:
1 | java复制代码@Target(ElementType.TYPE) |
到这里出现了两个import,这是干啥用的?看起来应该是很重要的东西。
那么来看看这个Import注解:
1 | java复制代码@Target(ElementType.TYPE) |
注解上写的是:标识一个或多个component类需要被引入,例如:@Configuration标识的类。
@Configuration注解可太熟了,注册数据源、redis之类的东西,都会写这么个玩意。
打开idea看看用到@Import注解的地方,可以发现其实大部分的enableXXX的注解,都是通过这玩意实现的,例如:
1 | java复制代码@Retention(RetentionPolicy.RUNTIME) |
那么此时就有问题了:
- 这个@Import是如何工作的?
- @Import中的类,又该如何编码呢?
@Import
继续通过idea,来看看@Import是在什么地方被处理的.
找了好久终于找到了两个类对于这个注解有处理:
- ConfigurationClassParser
- ConfigurationClassUtils
我们一个个看。
ConfigurationClassParser
收集import信息
我们是从这段代码中看到Import注解被处理的:
1 | java复制代码private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) |
方法上的注解写得很明白:
- 递归地将Import的值收集起来。
- 至于为什么是递归,下面也给了个例子:
1 | java复制代码For example, it is common for a @Configuration class to declare direct |
意思大概就是**@Configuration经常用一个@Import注解,来引入一些源于@Enable**注解的元导入。
也就是说一层是不够解析出要import的所有对象的,写代码的经常除了@Import之外给你用一些@EnableXXX的玩意,这些玩意在这里也不好直接解析(万一enable里再enable的情况咋整),所以这里就直接给开个递归解决这种问题。
这个方法粗浅一点看也大概知道是个什么意思:将sourceClass里包括的(以及可能存在的更多层次的)@Import里标识的那些类,给你塞到这个imports里。
这里的sourceClass不必深究,我们此时看到的代码层次只要知道是对于class对象的一个封装即可。【1】
上面方法的链路
上面的方法我们也看到了:
- 这其实就是个收集的方法
那么这里离我们要看到的如何注入的东西还是相差甚远的,此时我们在这个子问题中需要解决的是:
- 哪里调用来收集?
- 哪里处理已收集的信息?
我们先来看看这个方法的调用链:
doProcessConfigurationClassgetImportscollectImportsprocessImports
getImport就是个safe的代理方法,本身没什么就是保证两个list不是null。
上面的关系是从这里得出的:
1 | java复制代码// Process any @Import annotations |
看样子,processImports就是处理这些import注解的方法了。
import信息的处理
这里一共五十几行代码,为了方便对应啥意思就写代码上了,可能会出现阅读困难的地方做了标记,下面会解释:
1 | java复制代码private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, |
- 【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是什么东西?
本文转载自: 掘金