Spring源码(六):ConfigurationClassPostProcessor详解


        本篇主要详细说明ConfigurationClassPostProcessor。
        目前都是通过纯注解的方式使用Spring框架,虽然侵入性很强,但是能减少开发人员的工作量。基于注解的方式用的是AnnotationConfigApplicationContext上下文对象。

在这里插入图片描述

一、核心流程的入口

        进入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。
在这里插入图片描述

二、注解的收集

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

三、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进来的类上是否有其他注解。
在这里插入图片描述

四、其他注解的收集

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

五、注册BeanDefinition

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

六、处理@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。
在这里插入图片描述

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Session 是一个用于管理 Web 应用会话的框架,它提供了一种将会话信息存储在外部存储介质中的方法,以便多个应用程序实例可以共享相同的会话数据。在 Spring Session 中,会话的过期时间是通过在 Redis/数据库等外部存储介质中设置相应的 key 的过期时间来实现的。 具体来说,在 Spring Session 中,会话数据被存储在外部存储介质中的某个 key 下。而 key 的过期时间是由外部存储介质来维护的。例如,在 Redis 中,可以使用 `EXPIRE` 命令来设置 key 的过期时间;在数据库中,可以使用类似 `DELETE FROM session WHERE expire_time < NOW()` 的 SQL 语句来删除已经过期的会话数据。 在 Spring Session 中,会话过期时间的配置是在 `spring:session:sessions:expires` 属性中进行的。这个属性可以通过 Spring Boot 的配置文件或者通过 Java 代码来配置。具体来说,当 `spring:session:sessions:expires` 属性被配置为一个正整数时,表示会话的过期时间为该正整数所表示的秒数。当这个属性被配置为 `-1` 时,表示会话永不过期。 在 Spring Session 的码中,会话过期时间的处理是在 `org.springframework.session.data.redis.RedisOperationsSessionRepository` 类中完成的。具体来说,在 `RedisOperationsSessionRepository` 类中,会话数据被存储在 Redis 中的一个 key 下,而 key 的过期时间是通过调用 `RedisOperations#expire` 方法来设置的。在 `RedisOperationsSessionRepository` 类的 `afterPropertiesSet` 方法中,会根据 `spring:session:sessions:expires` 属性的值来设置 Redis 中 key 的过期时间。如果 `spring:session:sessions:expires` 属性的值为正整数,则会将 Redis 中 key 的过期时间设置为该正整数所表示的秒数;如果 `spring:session:sessions:expires` 属性的值为 `-1`,则会将 Redis 中 key 的过期时间设置为 0,表示该 key 永不过期。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值