这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」
前言
我们知道:
-
SpringBoot简化了Spring的配置,有了SpringBoot之后我们就不用写一大堆东西来引入中间件了,只需要我们在启动类上加上:
@SpringBootApplication 复制代码
并加上对应的中间件的注解,比如此时我们需要引入一个Eureka:
@EnableEurekaClient 复制代码
这样我们就能把对应的中间件引入到应用中了(当然,其他的什么地址之类的东西还是要配置的)。
那么这个功能是如何实现的呢?
@SpringBootApplication
首先,我们来看看这个注解:
@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
这个注解如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
复制代码
这里两个auto,我们先看看这个:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
复制代码
到这里出现了两个import,这是干啥用的?看起来应该是很重要的东西。
那么来看看这个Import注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
复制代码
注解上写的是:标识一个或多个component类需要被引入,例如:@Configuration标识的类。
@Configuration注解可太熟了,注册数据源、redis之类的东西,都会写这么个玩意。
打开idea看看用到@Import注解的地方,可以发现其实大部分的enableXXX的注解,都是通过这玩意实现的,例如:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
复制代码
那么此时就有问题了:
- 这个@Import是如何工作的?
- @Import中的类,又该如何编码呢?
@Import
继续通过idea,来看看@Import是在什么地方被处理的.
找了好久终于找到了两个类对于这个注解有处理:
- ConfigurationClassParser
- ConfigurationClassUtils
我们一个个看。
ConfigurationClassParser
收集import信息
我们是从这段代码中看到Import注解被处理的:
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的值收集起来。
- 至于为什么是递归,下面也给了个例子:
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】
上面方法的链路
上面的方法我们也看到了:
- 这其实就是个收集的方法
那么这里离我们要看到的如何注入的东西还是相差甚远的,此时我们在这个子问题中需要解决的是:
- 哪里调用来收集?
- 哪里处理已收集的信息?
我们先来看看这个方法的调用链:
graph LR
doProcessConfigurationClass-->getImports --> collectImports --> processImports
getImport就是个safe的代理方法,本身没什么就是保证两个list不是null。
上面的关系是从这里得出的:
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
复制代码
看样子,processImports就是处理这些import注解的方法了。
import信息的处理
这里一共五十几行代码,为了方便对应啥意思就写代码上了,可能会出现阅读困难的地方做了标记,下面会解释:
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)
这个接口提供了两个方法:
//根据元数据,返回需要导入的具体的class名称 String[] selectImports(AnnotationMetadata importingClassMetadata); //返回一个判断函数,这个函数如果返回的是true,代表输入的String不会被作为一个配置类处理 default Predicate<String> getExclusionFilter() { return null; } 复制代码
-
【A2】:
DeferredImportSelector
从类名可以看出来:延迟的importSelector,当然是继承ImportSelector的。
这个接口额外提供了一个方法:
Class<? extends Group> getImportGroup() 复制代码
这个ImportGroup,是这个接口中定义的一个子类,用于收集不同importSelector中的结果的。
上面说的AutoConfigurationImportSelector实际上实现的是这个接口。
而这里的handle其实做的事情就是把这个延迟的给存起来:
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相关了,该接口上的注释也是这么说的:
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方法有了更深一点的理解?
- 如果是importSelector:
小结
这里我们算是知道了:
- @import注解是在哪个方法中做的处理。
但就现在来看,我们只知道这个注解可能:
- 有多层次的情况
- 有循环import的可能
以及一些具体处理的细节。
但是具体处理的落地,在挖的坑【2】和【3】中。
这就意味着此时我们并没有看完整个过程的代码,只是流程中的一小部分,此时还涉及到:
- 这个方法在哪里被调用? - 这个事情很关键,我们需要知道@Import是如何工作的。
- 下面的坑要填 - 【2】【3】具体如何落地?
- 延迟的ImportSelector是如何工作的?
还有【1】,这个SourceClass是什么东西?