• 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

  • 相关阅读:
    Unity中传入任意数,转换成分,秒,并进行倒计时换算..(两种方式)
    Unity中调用手机中的粘贴功能
    关于Unity中IphoneX的适配
    可以接受多个字符串的可变参数
    可变参数函数示例
    多维数组指针指向引用问题
    printf的封装与实现
    关于MQTT的相关工具和使用(参考百度天工)
    单片机的堆和栈(Heap & Stack)详解
    单片机的内存分配(变量的存储位置)详解
  • 原文地址:https://www.cnblogs.com/chenxingyang/p/16126268.html
Copyright © 2020-2023  润新知