• Spring源码阅读 ConfigurationClassPostProcessor 配置类处理


    1. 概述

    当使用 spring-context 搭建简单 demo 时,调用 BeanDefinitionRegistryPostProcessor 第一个就是 ConfigurationClassPostProcessor
    image

    同时注意到它干了什么,它 getBean 立即将其转换为 Bean / 实例化了

    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
    

    2. 流程

    org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        // 一致性哈希,计算了一致性哈希或者没有重写 hashCode 但是调用了,那么这个对象的 synchronized 锁就不能再处于无锁状态了
        int registryId = System.identityHashCode(registry);
        // 这个是涉及多个上下文环境的那种处理吗
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        // 为什么上下分两个 ?
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);
    
        processConfigBeanDefinitions(registry);
    }
    
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        // 所有的 BD,注意这里包含 BeanFactoryPostProcessor , 当前类也包含,也就是说前面的 getBean 获取当前这个 BeanFactoryPostProcessor 并未将 BD 给删除
        String[] candidateNames = registry.getBeanDefinitionNames();
    
        // 由于上面是获取容器内所有 BD,因此可预见的是当前方法只会走一次,即便后续查找到其他 BD 也需要进行解析,也不会走当前方法
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            // 属性非空说明被解析过了
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            // 配置类: Configuration、ComponentScan、Bean、Import、ImportResource
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
    
        // Return immediately if no @Configuration classes were found
        // 为空,没有配置类需要解析,直接返回
        if (configCandidates.isEmpty()) {
            return;
        }
    
        // Sort by previously determined @Order value, if applicable
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });
    
        // Detect any custom bean name generation strategy supplied through the enclosing application context
        // BeanName 生成器, 那么如何自定义呢
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }
    
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
    
        // Parse each @Configuration class
        // 处理 @Configuration 注解的类
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
            // 解析配置类,从上下可分析出来,即便类似 @ComponentScan 查找到其他 BD,肯定也是直接进 Parser 内部的代码进行配置类解析了
            parser.parse(candidates);
            parser.validate();
    
            // 这里面应该是 Parser 记录的解析过的配置类
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            // 去除已经解析过的,这个实际主要是用在后面 loadBeanDefinitions 去除不必要的 BD, removeAll 是为了减少判断
            //  alreadyParsed 是上一次循环已经解析过的,去除上一次解析过的就是这一次解析过的,主要是 Parser 是复用的,所以需要 removeAll
            configClasses.removeAll(alreadyParsed);
    
            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            // 额, 这里对类似执行了一边 shouldSkip, 将不满足条件注入的BD从容器去除
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
            processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
    
            candidates.clear();
            // 什么情况会发生注入BD进了容器,但是却没有被解析呢?
            // 下面逻辑直接略, 反正是筛选出没有解析过的 BD 放到 candidates 循环过来解析
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());
    
        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
        // @Configuration 配置类需要,继承 ImportAware 接口即会传入配置类 @Import 注解的元数据信息
        if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
            sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
        }
    
        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            // Clear cache in externally provided MetadataReaderFactory; this is a no-op
            // for a shared cache since it'll be cleared by the ApplicationContext.
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }
    

    3. 具体配置类解析流程

    org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
    
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                // 一般是进入这里,其他的暂略
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
    
        this.deferredImportSelectorHandler.process();
    }
    
    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
    }
    
    protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
        // 这里也判断一遍, 在容器传入启动类使用 Reader 解析时传入的 ConfigurationPhase 为 null, 默认也是作为 PARSE_CONFIGURATION 进行解析的
        //    就是 Conditional 注解的解析,满足条件才会进行解析
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }
        // configurationClasses 保存着已解析的配置类
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        // 已经被解析过了
        if (existingClass != null) {
            // 那这个配置类是否是通过一个配置类的 @Import 导入的 或者说是一个配置类的内部类从而导入的
            //    内部类的情况是咋样?
            if (configClass.isImported()) {
                // 已存在的这个配置类也是被一个配置类 @Import 导入的或 ...
                if (existingClass.isImported()) {
                    // 合并,说明相同的一个类被两个配置类均 @Import 了,那么后面会造成什么情况?若其中一个配置类的@Conditional 不满足那么它就不会被注入容器?
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                // 说明这个类即被 @Import 了, 又被通过其他方式注入了, 略, 也即被其他方式注入的优先
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                // 当前配置类不是被导入的,那么不管前一个怎样,以后面的这个为准
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }
        }
    
        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass, filter);
        do {
            // 这个是处理父子类的问题,一般注入子类,那么后面会查看是否有父类,父类会返回为 sourceClass,再解析 sourceClass
            //    但是配置类还是认为是最初的子类 configClass
            sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
        }
        while (sourceClass != null);
    
        // 处理的配置类,可见这里就可能漏掉一些类,比如上面处理 configClass 可能返回其他 sourceClass, 那么这些被处理的 sourceClass 就没有被加入集合
        this.configurationClasses.put(configClass, configClass);
    }
    
    @Nullable
    protected final SourceClass doProcessConfigurationClass(
            ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
            throws IOException {
    
        // @Configuration、@... 等都是继承 Component
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // Recursively process any member (nested) classes first
            // 处理内部类的,也是暂略
            processMemberClasses(configClass, sourceClass, filter);
        }
    
        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }
    
        // Process any @ComponentScan annotations
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        // REGISTER_BEAN 阶段,那么和 PARSE_CONFIGURATION 阶段的不同?
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                // 指定包扫描,扫描包及其子包,实际最后还是使用 ClassPathBeanDefinitionScanner 来进行扫描的,MyBatis 扫描接口好像也是使用它扫描的
                //    ClassPathBeanDefinitionScanner 扫描到后会先立即注入BD,也就是这里返回的都是已经注入的BD
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    // 是否是配置类,扫描出来的这些需要判断是否为配置类,进入最初的配置类解析流程
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }
    
        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    
        // Process any @ImportResource annotations
        AnnotationAttributes importResource =
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }
    
        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }
    
        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);
    
        // Process superclass, if any
        // 若配置类作为子类,继承了一个父类,还会继续解析父类的数据
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") &&
                    !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                return sourceClass.getSuperClass();
            }
        }
    
        // No superclass -> processing is complete
        return null;
    }
    

    4. 几个点

    1. 配置类:@Configuration、@Component、@ComponentScan、@Import、@ImportResource

      • CONFIGURATION_CLASS_FULL:@Configuration + proxyBeanMethods 为 true(默认)
      • CONFIGURATION_CLASS_LITE:@Configuration + proxyBeanMethods 为 false、@Component、@ComponentScan、@Import、@ImportResource
      • 这些都是在 org.springframework.context.annotation.ConfigurationClassUtils 配置和处理的
    2. 问题:BeanDefinition 的分类?

    3. 处理的几个关键的注解:@Component、@ComponentScan、@Import、@ImportResource、@Bean

    4. ConfigurationClassPostProcessor

      • 继承 BeanDefinitionRegistryPostProcessor 接口
      • 配置类的处理,特别是 @ComponentScan 的处理
    5. ConfigurationClassParser

      • ConfigurationClassPostProcessor 实际是使用了一个这个实例进行具体的配置类解析的
  • 相关阅读:
    覆盖式发布与非覆盖式发布
    GIT
    Web Service返回符合Xml Schema规范的Xml文档
    下拉渐显菜单
    计算网页上坐标的距离
    初识交互设计
    良好用户体验-实现过程!
    做 用户调研?
    这个没什么技术含量,实现起来很简单?
    SQL SERVER 登录问题!该用户与可信的Sql Server连接无关联
  • 原文地址:https://www.cnblogs.com/chenxingyang/p/16120506.html
Copyright © 2020-2023  润新知