Spring源码阅读 - importStack

1. 使用的地方

是一个 ConfigurationClassParser 的成员变量,主要是用于判断循环导入?
image

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 呢

  1. processMemberClasses 处理内部类还未完成,处理内部类过程中导致 configClass 被二次当做配置类处理,进入到这
    那么下面的调用肯定返回 null,因为内部类的处理没有涉及到 MultiValueMap 这个集合,而 getImportingClassFor 是从这个集合取的。
  2. @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

posted @ 2022-04-10 16:10  YangDanMua  阅读(199)  评论(0编辑  收藏  举报