Spring源码阅读 - importStack
1. 使用的地方
是一个 ConfigurationClassParser
的成员变量,主要是用于判断循环导入?
2. ImportStack
3. 第一处使用 ConfigurationClassParser#processMemberClasses
这个是处理一个类的内部类的,包括静态内部类和实例内部类
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
// @Configuration、@... 等都是继承 Component
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
org.springframework.context.annotation.ConfigurationClassParser#processMemberClasses
/**
* Register member (nested) classes that happen to be configuration classes themselves.
* 处理一个配置类的内部类的, 包括静态内部类和实例内部类, 所有访问级别的, 不含继承而来的内部类
*/
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
Predicate<String> filter) throws IOException {
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
// 内部类是否为配置类
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
// emm, 这个是什么时候会相等呢?
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
// 注意下面 contains、push、pop 的都是 configClass
// 同时可看出, 对于一个配置类A的内部配置类B, 相当于 A @Import(B) 进行处理, 那么下面 CircularImportProblem 的情况就可能是
// A 中的内部类 B 使用注解标明自己需要被容器管理, 然后 B @Import(A) 造成循环导入的情况
for (SourceClass candidate : candidates) {
//
if (this.importStack.contains(configClass)) {
// 内部实现就是抛出异常, 这里就是出现循环导入则抛出异常
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 把这个内部类当做一个配置类进行解析, 解析过程没有注入 BD, 需要后续处理这些遗漏的 BD
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
// 注意上面 push 后这里立即 pop
this.importStack.pop();
}
}
}
}
}
说实话,Spring 把这种内部类的处理和 @Import 处理合并,但其实又有点区别的处理合并起来,比较难以理解。
3. 第二处使用 ConfigurationClassParser#processImports
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 导入的类为空
if (importCandidates.isEmpty()) {
return;
}
// 循环导入的情况,A @Import(B), 然后 B 也 @Import(A), 不支持这样的情况
// 内部判断逻辑暂时不理解
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
// push 了配置类, 注意到后面最终 pop 了
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// Import 的类继承了 ImportSelector 接口
if (candidate.isAssignable(ImportSelector.class)) {
// ...
}
// 继承了 ImportBeanDefinitionRegistrar 接口,说明这个类有想自己向容器注入 BD 的想法,比如说 MyBatis 自己收集接口, 自己注入这些接口代理类的 BD
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// ...
}
else {
// 普通类,这里的普通类包括 @Import 直接导入的没有继承上面三个接口的类,还有就是 @Import 导入了继承 ImportSelector 接口的类要注入的普通类
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 注意到 registerImport 和 push、pop 存储的内部容器都是不一样的
// 这里相当于将这个被导入的类视为是被这个父类导入的,而非这个配置类导入的
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();
}
}
}
org.springframework.context.annotation.ConfigurationClassParser#isChainedImportOnStack
private boolean isChainedImportOnStack(ConfigurationClass configClass) {
if (this.importStack.contains(configClass)) {
String configClassName = configClass.getMetadata().getClassName();
// 判断这个配置类是否被其他类 @Import 导入,问题是这里仅取了一个,理论上不是可能被多个导入吗
AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName);
// 说明当前配置类确实被其他班配置类导入了
while (importingClass != null) {
// 相等,说明存在循环导入的情况
if (configClassName.equals(importingClass.getClassName())) {
return true;
}
// 不等,则从导入链向上查找
importingClass = this.importStack.getImportingClassFor(importingClass.getClassName());
}
}
return false;
}
4. ImportStack
ImportStack
是一个私有的内部类,继承了 ArrayDeque,还有一个 MultiValueMap 字段。
实际上上面的 pop、push 都是针对 ArrayDeque 这个容器,而 getImportingClassFor、registerImport 才是针对 MultiValueMap 这个容器。
private static class ImportStack extends ArrayDeque<ConfigurationClass> implements ImportRegistry {
// 一个 key 多个 value 的集合实现
private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>();
}
5. 内部类情况
先不考虑 @Import 的影响,就是判断 ArrayDeque 中是否有当前要处理的 configClass,有则视为存在 CircularImportProblem。
将内部类当做配置类处理前,configClass 先 psuh,处理内部类完毕,再pop。
简单考虑,就是在处理内部类的过程中,可能再次将当前 configClass 入栈了但是没有出栈,那么可能是什么情况呢?肯定内部类对外部类作用了,而且需要这个内部类处理完毕后,外部类还遗留在栈中。
那么 push 的地方有两处 processMemberClasses 和 processImports,都是将要处理的 configClass 进行 push,那么可能出现的问题就是,处理这个 configClass 的内部类时,这个内部类处理过程中又再次导入了这个 configClass 导致它第二次被视为配置类处理,也就只能被 @Import 注解再次导入喽。(问题:ComponengScan 和这些导入不会冲突吗)
6. @Import 情况
@Import 处理是单独使用了 isChainedImportOnStack 函数进行判断的。
private boolean isChainedImportOnStack(ConfigurationClass configClass) {
if (this.importStack.contains(configClass)) {
String configClassName = configClass.getMetadata().getClassName();
// 判断这个配置类是否被其他类 @Import 导入,问题是这里仅取了一个,理论上不是可能被多个导入吗
AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName);
// 说明当前配置类确实被其他班配置类导入了
while (importingClass != null) {
// 相等,说明存在循环导入的情况
if (configClassName.equals(importingClass.getClassName())) {
return true;
}
// 不等,则从导入链向上查找
importingClass = this.importStack.getImportingClassFor(importingClass.getClassName());
}
}
return false;
}
栈中有当前 configClass 才进行处理,那么什么情况导致栈中可能有这个 configClass 呢
- processMemberClasses 处理内部类还未完成,处理内部类过程中导致 configClass 被二次当做配置类处理,进入到这
那么下面的调用肯定返回 null,因为内部类的处理没有涉及到 MultiValueMap 这个集合,而 getImportingClassFor 是从这个集合取的。 - @Import 接口导入一个类(导入普通类和导入ImportSelector接口的类),被导入的这个类反过来又导入了这个 configClass 被当做配置类处理从而进入这
首先 @Import 一个普通类和 @Import 一个ImportSelector接口的类再从这个接口获取类,基本是一样的,后面以导入普通类说明。
导入普通类,这些普通类会被当做配置类处理,在被处理前,会先
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
这里会将 importedClass 类名 --> sourceClass 数据映射起来,那么通过 configClass 来 getImportingClassFor 就可以看出这个 configClass 是否有被 @Import,或者有一个 @Import 链,而这个 configClass 被链上的某个类导入了。
如果出现 @Import 的循环,那么肯定是 configClass 处理,@Import 一个类 --> 处理这个 importedClass,它又 @Import 了类 ----> ... --> @Import(configClass),从链上必然又能找到这个 configClass