禅与计算机程序设计艺术编程语言爱好者spring

spring之ConfigurationClassPostPro

2022-11-04  本文已影响0人  pq217

前言

spring后置处理器哪家强?一定是ConfigurationClassPostProcessor这个后置处理器

单从名字上看,是一个处理配置类的后置处理器

从功能上来讲,基本上所有用户定义的bean都是又ConfigurationClassPostProcessor加入到容器的

从角色角度来讲,用户实现BeanFactory后置处理器(包括很多第三方整合spring所实现的后置处理器),都是由ConfigurationClassPostProcessor负责注册到spring容器,才会在ApplicationContext执行后置处理器阶段被实例化并执行其后置处理方法

定位

如果把BeanFactory后置处理器比喻成,其bean定义比喻成,ConfigurationClassPostProcessor可以说是spring生命周期中的第一个,是一个创世纪的

ConfigurationClassPostProcessor首先是一个BeanFactory后置处理器,而且是具有bean定义注册能力的BeanDefinitionRegistryPostProcessor,刚说他是一个创世纪的后置处理器,那么他是从哪里来的呐?

实际上,它是在ApplicationContext初始化时加入到spring容器中,并在初始化后的invokeBeanFactoryPostProcessors(执行后置处理器)被实例化并执行其后置处理方法,此时一般情况只有这一个唯一的BeanFactory后置处理器

在其后置处理方法执行过程,会注册用户定义的bean定义至容器,有可能会扫描并向容器中加入新的用户实现BeanFactory后置处理器,这些后置处理器也会在invokeBeanFactoryPostProcessors阶段被陆续执行,如果这些后置处理器又注册了其它BeanFactory后置处理器,依然会在这个阶段被陆续执行,像是一个蛋生鸡,鸡生蛋的过程

invokeBeanFactoryPostProcessors

关于这些问题,我再另一篇文章一文通透spring的初始化有详细介绍,本文重点分析ConfigurationClassPostProcessor的功能,这里只简单知道它的定位和执行时机即可

名词

再看源码之前,有些名词首先要理解

配置类

ConfigurationClassPostProcessor是一个处理配置类的后置处理器,那么什么样的类才是配置类?

初看这个名称以为是带有@Configuration注解的类,事实也确实如此,但不仅限如此,配置类分两种

spring有一个工具方法用来判断某类的bean定义是否为配置类,即方法ConfigurationClassUtils.checkConfigurationClassCandidate,该方法返回true就代表是配置类

checkConfigurationClassCandidate
parser(ConfigurationClassParser)

解析器,把配置类bean定义及其所有关联类的信息封装为ConfigurationClass,这里的关联类主要指通过@ComponentScan指定的路径、@Import引入的类等进行关联,其中@ComponentScan扫描到的类会立即注册到bean容器

reader(ConfigurationClassBeanDefinitionReader)

读取器,主要承担读取parser解析出的ConfigurationClass,将其它形式的关联类注册到bean定义容器,如@Import引入的类, @Bean方法创建的bean

parser 和 reader

ConfigurationClassParser有一段这样的注释:

This class helps separate the concern of parsing the structure of a Configuration class from the concern of registering BeanDefinition objects based on the content of that model (with the exception of {@ComponentScan} annotations which need to be registered immediately)

spring这分这两个类的本意是为了把配置类的解析注册bean定义的逻辑分离开,但是@ComponentScan注解是个特例,它路径下的配置类会被立即注册到spring容器

说实话,不太理解spring为什么既然要拆分逻辑,但又拆分的不彻底,注释中所说的特例@ComponentScan实际上是最常用且数量最多的bean定义,所以大部分bean定义注册的活还是parser承担的,而本应该肩负注册bean定义职责的reader其实只注册了很少一部分的bean定义

如果我是设计者,我更希望parser全权负责解析配置类至ConfigurationClass对象,reader全权负责阅读ConfigurationClass对象并注册bean定义,这样应该更符合单一职责吧

源码解析

ConfigurationClassPostProcessor作为一个bean定义注册后置处理器,最重要的方法必然是postProcessBeanDefinitionRegistry,那么接下来就看看在这个方法里它做了哪些事(以下代码省略一些支线逻辑)

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // 省略一些判断...
    processConfigBeanDefinitions(registry);
}

processConfigBeanDefinitions:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 候选配置类定义列表
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 获取容器中所有bean定义的名字
    String[] candidateNames = registry.getBeanDefinitionNames();
    // 循环名字
    for (String beanName : candidateNames) {
        // 获取bean定义
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
            // 省略...
        }
        // 查看bean是否是ConfigurationClass
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            // 加入到候选配置类定义列表
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }

    // 没有@Configuration的bean就返回
    if (configCandidates.isEmpty()) {
        return;
    }

    // 省略排序,beanName生成规则,环境变量等...

    // 构造一个配置类解析器,用来把bean定义的重要信息提取转化为ConfigurationClass
    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 {
        // 解析配置类
        parser.parse(candidates);
        parser.validate();

        // 获取解析器中解析出的配置类ConfigurationClass:parser.getConfigurationClasses()
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        // 过滤掉已解析的配置类
        configClasses.removeAll(alreadyParsed);

        // 构造一个bean定义读取器
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        // 读取ConfigurationClass获取衍生bean定义并注册到容器
        this.reader.loadBeanDefinitions(configClasses);
        // 加入已解析配置类
        alreadyParsed.addAll(configClasses);
        // 清空候选配置类定义列表
        candidates.clear();
        // 如果容器中bean定义有新增
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            /** 查找出新增的配置类bean定义 start **/
            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));
                    }
                }
            }
            /**查找出新增的配置类bean定义 end , 最终新的配置bean定义加入到候选配置类定义列表 **/
            // 覆盖所有beanName,为下一次判断是否有新增
            candidateNames = newCandidateNames;
            
        }
    }
    while (!candidates.isEmpty()); // 只要候候选配置类定义列表不为空就一直循环下去

    // 省略...
}

上面的代码比较长,但流程很清晰,总结一下

  1. 查找spring容器中的配置类bean定义,存储候选列表
  2. 使用解析器,解析候选列表中的配置类bean定义,根据配置的扫描范围把相关的类都解析为ConfigurationClass对象(其中@CommpentScan下扫描的类直接注册bean定义)
  3. 使用读取器,读取上一步解析出的新未解析ConfigurationClass,注册相关bean定义
  4. 清空候选列表
  5. 继续查询spring容器,如果有新增的配置类bean定义,读取加入候选列表,跳转回第2步

画个图清晰一下

processConfigBeanDefinitions

为什么第5步会有新增呐?是因为第2/3步是有可能注册新的配置类bean定义至容器,只想说spring太喜欢玩这种把戏了

通过代码分析也可以得知,ConfigurationClassPostProcessor重点使用了两大利器:parser(解析器) 和 reader(读取器),接下来就具体分析二者都干了什么活

Parser

上文ConfigurationClassPostProcessor对parser具体使用主要有三处

parser

configurationClasses

ConfigurationClassParser中一个属性configurationClasses,用来存放解析出来的结果

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();

对应的get方法,就是ConfigurationClassPostProcessor最终获取解析结果的方法

public Set<ConfigurationClass> getConfigurationClasses() {
    return this.configurationClasses.keySet();
}

解析(parse)的重点责任就是根据传入的参数(bean定义列表)转化为Map<ConfigurationClass, ConfigurationClass>,下面重点分析分析方法:parse方法:

parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    // 循环
    for (BeanDefinitionHolder holder : configCandidates) {
        // 获取bean定义
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // 根据bean定义的类型走不通参数的重载parse方法
            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...
    }
    this.deferredImportSelectorHandler.process();
}

因为是多bean定义参数,所以这里主要是循环再根据不同类型走重载的parse方法,这些重载方法主要是适配各种情况,最终都会根据bean定义生成一个ConfigurationClass对象传入processConfigurationClass方法

processConfigurationClass

注这里最后有一步this.deferredImportSelectorHandler.process(),先注意一下后面会说到

processConfigurationClass

processConfigurationClass这个方法贴主要代码

do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
// 
this.configurationClasses.put(configClass, configClass);

又是一个循环,调用doProcessConfigurationClass方法,如果有返回值,递归调用doProcessConfigurationClass,看spring的注释说递归解析配置类和他的父类,所以这代码的意思就是解析配置类,如果有父类再解析父类,如果父类有父类再一直解析下去。

最后this.configurationClasses.put(configClass, configClass);就是把上一步根据bean定义生成的ConfigurationClass对象存入解析的结果map:configurationClasses,实际上,对解析结果的添加只有这一处代码

如果单单看到这里,parser的功能就是把传入的多个bean定义循环转换为ConfigurationClass并存入解析结果中,相当于传入多少bean定义就转换多少ConfigurationClass对象

单parse方法的功能远不止如此,重点在doProcessConfigurationClass方法

doProcessConfigurationClass

重点来到了doProcessConfigurationClass方法:

protected final SourceClass doProcessConfigurationClass(
        ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
        throws IOException {
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // 1.内部类  这一步看看有没有内部类
        processMemberClasses(configClass, sourceClass, filter);
    }

    // 2.@PropertySource 这一步解析@PropertySource注解,更改配置文件位置时会使用,一般使用默认位置,不咋更改
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), PropertySources.class,
            org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        // 省略...
    }

    // 3.@ComponentScan 这一步就很重要了,解析@ComponentScan注解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            // 开始扫描,把@ComponentScan指定包下的@Component类全部扫描出来,并且会注册bean定义至容器
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // 把所有扫描到的beanClass递归解析
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    // 获取bean定义
                    bdCand = holder.getBeanDefinition();
                }
                //判断是不是配置类
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    // 解析
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // 4.@Import 解析@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // 5.@ImportResource解析@ImportResource解析
    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);
        }
    }

    // 6.@Bean 解析带有@Bean注解的方法,加入到configClass的beanMethods属性中
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 6.接口@Bean 解析实现的接口中带有@Bean注解的默认方法,加入到configClass的beanMethods属性中
    processInterfaces(configClass, sourceClass);

    // 7.父类 如果有父类返回父类,以继续解析
    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();
        }
    }

    // 没有父类,解析结束
    return null;
}

这个方法很长,主要包含以下几个功能

所以doProcessConfigurationClass这一步的重点工作就是扫描某个配置类的注解,根据注解功能找到指定扫描范围的类,递归解析(调用parse或processConfigurationClass方法)

比如@ComponentScan就是扫描到basePackages指定路径下的类,然后通过递归调用parse解析这些类,@Import通过value属性指定的类,递归调用processConfigurationClass解析

重点这个递归,有了它就可以实现一个bean定义解析出多个ConfigurationClass对象

这里有个特例,@ComponentScan扫描的类会被直接注册到spring容器

接下来再把这几个注解的解析过程详细介绍一下

解析内部类
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    // 1.内部类  这一步看看有没有内部类,一般不咋用
    processMemberClasses(configClass, sourceClass, filter);
}

也就是说如果一个类有@Component注解,processMemberClasses内部会解析当前类的内部类,如果是配置类,则会递归调用processConfigurationClass去解析这个内部类

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()) &&
                    !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
                candidates.add(memberClass);
            }
        }
        OrderComparator.sort(candidates);
        // 循环候选列表
        for (SourceClass candidate : candidates) {
            if (this.importStack.contains(configClass)) {
                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
            }
            else {
                this.importStack.push(configClass);
                try {
                    // 递归解析内部类
                    processConfigurationClass(candidate.asConfigClass(configClass), filter);
                }
                finally {
                    this.importStack.pop();
                }
            }
        }
    }
}

这就是为什么内部类也能加入bean容器中

解析@PropertySource注解

这个真没用过,主要是自定义配置文件,我觉得配置文件放在spring约定的地就不错,这个就不研究了

解析@ComponentScan

这个都懂,就是扫描的包路径,这一步的代码细分析下

(1) 扫描包下的类

Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

这个componentScanParser内部有个scaner(扫描器),扫描@Component注解的类,包括子注解@Configuration@Service等(这过程还会直接把扫描到的类注册bean定义)

scaner

(2) 循环判断扫描到的类是否是ConfigurationClass,如果是则递归解析

// 如果是ConfigurationClass
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    // 解析
    parse(bdCand.getBeanClassName(), holder.getBeanName());
}
解析@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

这个也比较重要,参数getImports方法获取到了当前配置类所有@Import注解指定的类集合,然后进入processImports

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
        Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
        boolean checkForCircularImports) {

    // 省略...
    
    // 循环所有@Import指定的类
    for (SourceClass candidate : importCandidates) {
        // 如果这个类实现了ImportSelector,即导入多个类
        if (candidate.isAssignable(ImportSelector.class)) {
            // 实例化这个导入的ImportSelector类
            ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                            this.environment, this.resourceLoader, this.registry);
                            
            // 省略一些过滤...
            
            // 如果是延迟导入,交给deferredImportSelectorHandler处理
            if (selector instanceof DeferredImportSelector) {
                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
            }
            // 普通的多个导入
            else {
                // 找到导入的多个类
                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                // 递归调用当前方法
                processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
            }
        }
        // 如果这个类实现了ImportBeanDefinitionRegistrar
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // 实例化这个ImportBeanDefinitionRegistrar类
            ImportBeanDefinitionRegistrar registrar =
                            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                    this.environment, this.resourceLoader, this.registry);
            // 加入该类到当前配置类解析对象ConfigurationClass的importBeanDefinitionRegistrars列表属性中
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        // 排除上面两种情况,这个类就是个普通类
        else {
            // 省略...
            // 递归解析这个类
            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
        }
    }
        
}

这里涉及的内容较多,主要分几种情况

解析@ImportResource注解

主要为了兼容之前的xml写法,不做研究

解析@Bean

@Bean注解一般经常使用,使用工厂方法创建一个bean,一般就是@Configuration+@Bean,当然@Component+@Bean也可以

// 获取所有@Bean注解方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
// 包装成BeanMethod加入到配置类beanMethods属性中
for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

这里通过解析当前配置类@Bean方法,并将方法打包为BeanMethod存入配置类beanMethods属性中

解析实现接口中的@Bean

这是对@Bean注解的一个扩展,解析配置类实现的接口中带有@Bean注解的默认方法,即某个配置类实现的接口有@Bean注解也会和普通的@Bean方法一样被加入到配置类beanMethods属性中

返回父类

最后一步如果返回父类继续递归解析,这个不做过多介绍了

总结Parser

基本上parser内部解析的重点方法都分析完了,总结一下,parser都干了什么?

所以最终结果是把根配置类下的整个树扫描,生成多个ConfigurationClass对象,同时@ComponentScan扫描到的配置类会直接注册bean定义

最后画个图梳理整个解析过程

parser

DeferredImportSelector只不过是延迟导入,从整个解析过程来看最终结果与@Import一样

Reader

相比于parser,reader的任务轻松多了,主要任务就是把parser解析出的ConfigurationClass集合读取并转换为bean定义,并最终注册到spring容器

reader.loadBeanDefinitions:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    // 循环ConfigurationClass集合
    for (ConfigurationClass configClass : configurationModel) {
        // 读取ConfigurationClass并注册bean定义
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}

但是上面parser已经把@ComponentScan扫描到的配置类注册完了,reader注册什么呐,看一下loadBeanDefinitionsForConfigurationClass代码是如何注册每个ConfigurationClass对象的

private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    // 省略的

    // 注册被Import引入的类,标识ConfigurationClass.isImported==true
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    // 注册@Bean的工厂方法,存储在ConfigurationClass.beanMethods属性中
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }

    // ImportedResource(忽略)
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 执行ImportBeanDefinitionRegistrar实现注册用户自定义bean定义,存储在ConfigurationClass.importBeanDefinitionRegistrars属性中
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

Parser注册了@ComponentScan扫描的bean定义,而Reader主要注册了这几个bean定义

这么一看Reader反而成了捡漏的了...,就是把Parser没注册的bean定义都注册上

其中loadBeanDefinitionsFromRegistrars这步就是执行ImportBeanDefinitionRegistrar实现的registerBeanDefinitions方法,即用户自定义注册bean常用的@Import+ImportBeanDefinitionRegistrar模式的实际执行时机

reader

总结

本文完整的示意图如下

ConfigurationClassPostProcessor

以上就是ConfigurationClassPostProcessor源码的详解,可能有错过的点,以后遇到再补充

上一篇 下一篇

猜你喜欢

热点阅读