spring ConfigurationClassPostProcessor详解

@Configuration 是 SpringBoot 配置的基石,自然 @Configuration 类的处理是很有必要研究的。

@Configuration 类的处理是由 ConfigurationClassPostProcessor 来处理的。
本篇主要详细说明ConfigurationClassPostProcessor。
目前都是通过纯注解的方式使用Spring框架,虽然侵入性很强,但是能减少开发人员的工作量。基于注解的方式用的是AnnotationConfigApplicationContext上下文对象。
在这里插入图片描述

1 核心流程的入口

进入this,来到AnnotatedBeanDefinitionReader,可以看到registerAnnotationConfigProcessors中注册了ConfigurationClassPostProcessor。
在这里插入图片描述
ConfigurationClassPostProcessor的核心逻辑在postProcessBeanDefinitionRegistry的processConfigBeanDefinitions中,由此可知processConfigBeanDefinitions是bean在实例化前调用的(postProcessBeanDefinitionRegistry在bean实例化之前执行,具体可以看这篇 Spring在Bean实例化前的操作)。
获取所有的beanName,看BeanDefinition中是否有处理完成的标识。没有处理完成的标识则在checkConfigurationClassCandidate中判断是否是候选的需要处理的BeanDefinition,如果是就放入容器configCandidates。
在这里插入图片描述
首先判断BeanDefinition是扫描注解产生的还是自己添加的,拿到metadata对象,metadata中包含了类的注解信息。
在这里插入图片描述
根据metadata中的注解判断是full匹配还是lite匹配。
在这里插入图片描述
在这里插入图片描述
创建解析器、需要解析的BeanDefinitionHolder容器和已经解析过的BeanDefinition容器。
在这里插入图片描述
在parse中可以看到具体的解析过程。基于注解的BeanDefinition一般是AnnotatedBeanDefinition的实现类ScannedGenericBeanDefinition,手动添加的BeanDefinition一般是AbstractBeanDefinition的子类GenericBeanDefinition和RootBeanDefinition。ScannedGenericBeanDefinition又是GenericBeanDefinition的子类,于是就有了下面这个解析顺序。
在这里插入图片描述
扫描注解的parse方法中,先把metadata和beanName包装成ConfigurationClass对象,在processConfigurationClass中再把ConfigurationClass和filter包装成SourceClass。
在这里插入图片描述

2 注解的收集

进入doProcessConfigurationClass,判断类上面是否有Component注解,递归处理有候选注解的内部类,把父类放到importedBy容器中。
处理PropertySources和 PropertySource注解,创建PropertySource对象加入到Environment对象中。
处理ComponentScans和ComponentScan注解,扫描到@Component生成beanDefinition后,还要递归去校验类上面是否有其他特殊注解,也是通过ConfigurationClassParser.processConfigurationClass处理。
下面是处理@Import注解的流程。getImports中收集@Import注解和注解中的值。
在这里插入图片描述

3 ImportSelector接口和DeferredImportSelector接口和ImportBeanDefinitionRegistrar接口

在processImports中循环处理@Import注解。这里会处理ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar接口。因此,某个类实现ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar接口后,只有被@Import,对应的接口方法才会被处理,加上@Component注解反而不会被处理。实现这3类接口的类都不会被加入Spring容器。
在这里插入图片描述
在这里插入图片描述
处理ImportSelector接口时,会调用selectImports方法,方法的返回值是要加入到spring容器的类全限定名,方便拿到metaData对象。selectImports的方法参数是当前有@Import注解的类的AnnotationMetadata对象,也就是说在实现了ImportSelector接口的selectImports方法中可以拿到有@Import注解的类的相关信息。
比如说,ImportBean @Import了LisonSelectImport,在LisonSelectImport的selectImports方法中能拿到ImportBean的EnableAspectJAutoProxy注解相关属性。SpringBoot中也是用这种方式实现自动配置的。
在这里插入图片描述
在这里插入图片描述
DeferredImportSelector 是ImportSelector的子接口,如果被import的类是DeferredImportSelector类型,则会看到下图中的结构。
getImportGroup方法用来返回一个实现了Group接口的类,只要返回了,Group当中的process和selectImports会被调到。Group是DeferredImportSelector的内部接口,包含process、selectImports方法和一个内部类Entry。process主要用来收集要被Spring实例化的类,包装成Entry对象,Entry的属性包括有@Import注解的类的metaData对象和需要实例化的类名。selectImports将Entry对象集合返回。
在这里插入图片描述
下面是DeferredImportSelector实现类的例子。

public class DeferredImportSelectorDemo implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 这个方法不主动调不会调到
        System.out.println("=====DeferredImportSelectorDemo.selectImports");
        return new String[]{SelectImportBean.class.getName()};
    }

    /**
     * 要返回一个实现了Group接口的类,process和selectImports就会被调到
    */
    @Override
    public Class<? extends Group> getImportGroup() {
        return DeferredImportSelectorGroupDemo.class;
    }

    private static class DeferredImportSelectorGroupDemo implements DeferredImportSelector.Group {

        List<Entry> list = new ArrayList<>();
        /**
            收集需要实例化的类
        */
        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            System.out.println("=====DeferredImportSelectorGroupDemo.process");
            String[] strings = selector.selectImports(metadata);
            for (String string : strings) {
                list.add(new Entry(metadata,string));
            }
        }

        /**
         返回结果
         */
        @Override
        public Iterable<Entry> selectImports() {
            System.out.println("=====DeferredImportSelectorGroupDemo.selectImports");
            return list;
        }
    }
}

接着看DeferredImportSelector的原理。进入到deferredImportSelectorHandler.handle方法,首先将有@Import注解的类和被import进来的实例化对象包装成DeferredImportSelectorHolder。
在这里插入图片描述
当deferredImportSelectors为null时,创建DeferredImportSelectorGroup接口的处理类后进入register。
在这里插入图片描述
调用getImportGroup方法,返回实现了Group接口的类。createGroup中构建group的实例对象,如果getImportGroup方法返回的类为null,则用Spring的DefaultDeferredImportSelectorGroup.class构建DeferredImportSelectorGrouping对象,建立实现了Group接口类和DeferredImportSelectorGrouping的映射关系。如果getImportGroup方法返回的类为null,则key为DeferredImportSelectorHolder。
最后,把DeferredImportSelectorHolder对象加入到DeferredImportSelectorGrouping的deferredImports容器中。拿到带有@Import注解的类的Metadata对象,把它放到configurationClasses容器。
在这里插入图片描述
接着调用handler.processGroupImports();遍历groupings的values。
在grouping.getImports()方法中,遍历deferredImports容器,调用group的process方法收集Entry,参数为带@Import注解的类的metaData对象和被Import的实例化对象。然后调用group的selectImports方法返回Entry集合。如果group是DefaultDeferredImportSelectorGroup类型,则会调用被import类的selectImports方法,将返回的完整限定名包装为Entry,Group的selectImports方法返回Entry集合。
在这里插入图片描述
遍历返回的Entry集合,每一个Entry都走@Import的处理逻辑。
在这里插入图片描述
如果deferredImportSelectors不为空,在收集完DeferredImportSelectorHolder后,外层ConfigurationClassParser.parse中的this.deferredImportSelectorHandler.process();处理每一个DeferredImportSelectorHolder,调用DeferredImportSelectorGroupingHandler的register方法和processGroupImports方法。

    如果Import进来的是一个ImportBeanDefinitionRegistrar类型,会反射实例化,然后加入到容器中,这个容器是实例对应AnnotationMetadata的Map。ImportBeanDefinitionRegistrar接口有默认的registerBeanDefinitions方法,这个方法可以创建BeanDefinition对象并加入到BeanDefinitionRegistry,此时并不会被调用。

在这里插入图片描述
假如没有实现上述三种接口,则会走ConfigurationClassParser.processConfigurationClass,看被import进来的类上是否有其他注解。
在这里插入图片描述

4 其他注解的收集

Import注解处理完后。接下来处理@ImportResource注解,建立xml文件和reader的映射关系,这个是加载xml配置文件的,没啥用。处理@Bean注解,收集有@bean 注解的方法,将MethodMetadata对象和当前类包装后,加入beanMethods容器中。
在这里插入图片描述
收集完注解后,将当前类放入map容器configurationClasses。
在这里插入图片描述

5 注册BeanDefinition

接着来到ConfigurationClassPostProcessor.processConfigBeanDefinitions的this.reader.loadBeanDefinitions(configClasses);
这个里面把@Import进来的类、内部类、@Bean注解的方法等封装成BeanDefinition并且注册到BeanDefinitionRegistry。@Bean的处理过程中,会设置BeanDefinition的factoryBeanName为当前类,设置factoryMethodName为当前方法,这和Bean的实例化过程的第一种情况是一样的。
然后是xml的解析流程、循环调用ImportBeanDefinitionRegistrar的方法。
在这里插入图片描述

6 处理@Configuration注解

在 Spring在Bean实例化前的操作中介绍过,postProcessBeanDefinitionRegistry方法执行完后执行postProcessBeanFactory方法,这个方法的核心处理流程在enhanceConfigurationClasses。
拿到所有full标记的BeanDefinition,即有@Configuration的类,将其添加到Map容器中。
在这里插入图片描述
循环Map,对每个BeanDefinition生成代理。
在这里插入图片描述
ConfigurationClassEnhancer.enhance方法是对每个BeanDefinition生成代理的具体处理逻辑,这里定义了Enhancer、CALLBACKS,最后返回代理类,交给Spring去实例化。
在这里插入图片描述
在这里插入图片描述
假如@Configuration的类中有@Bean的方法,会在accept中匹配到BeanMethodInterceptor。
在这里插入图片描述
如果是Spring实例化Bean,会来到下图这里,此时priorInvokedFactoryMethod为null,factoryMethod为@Bean
方法,在invoke的时候就会走到BeanMethodInterceptor的代理逻辑中。
在这里插入图片描述
isCurrentlyInvokedFactoryMethod判断为true,看@Bean方法的返回类型是不是BeanFactoryPostProcessor,然后再调用方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果在一个@Bean的方法中调用另一个@Bean的方法,是用代理对象调的,比如说下面这个例子。
在这里插入图片描述
在这里插入图片描述
此时currentlyInvokedFactoryMethod存的是lisonFactory,所以isCurrentlyInvokedFactoryMethod判断为false,执行resolveBeanReference,核心代码为

Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
						beanFactory.getBean(beanName));
    所以无论何时调用@Configuration类里面@Bean方法产生的对象,它都是同一个对象。比如说,下面两种情况lison的hashCode绝对是一样的。而@Component不会生成代理对象,把@Configuration换成@Component会得到不同的hashCode。

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值