Spring 源码学习(六)扩展功能 上篇(上)

简介: 结束了前面的基础结构分析,了解到 Spring 是如何识别配置文件和进行解析属性,最终将 bean 加载到内存中。同时为了更好得理解 Spring 的扩展功能,我们先来巩固一下 beanFactory 和 bean 的概念,然后再分析新内容后处理器 PostProcessor 。

本篇阅读思路:

  1. 是什么
  2. 如何使用
  3. Spring 实现逻辑

  • BeanFactoryPostProcessor
  • 是什么
  • 如何使用
  • 官方例子:PropertyPlaceholderConfigurer
  • 使用自定义 BeanFactoryPostProcessor
  • 在哪注册
  • 激活 BeanFactoryPostProcessor
  • 流程图
  • 代码
  • 总结
  • 参考资料

前言

首先我们先将 Spring 想像成一个大容器,然后保存了很多 bean 的信息,根据定义:bean 是一个被实例化,组装,并通过 SpringIoC 容器所管理的对象,也可以简单得理解为我们在配置文件配置好元数据, SpringIoC 容器会帮我们对 bean 进行管理,这些对象在使用的时候通过 Spring 取出就能使用。

那么是谁帮这个 Spring 管理呢,那就是 BeanFactory,粗暴点直译为 bean 工厂,但其实它才是承担容器功能的幕后实现者,它是一个接口,提供了获取 bean 、获取别名 Alias 、判断单例、类型是否匹配、是否原型等方法定义,所以需要通过引用,实现具体方法才后才能使用。

回顾完 beanFactory 后,我们再来回顾在前面内容中,看到过很多后处理器 PostProcessor 的代码影子,分别是 BeanFactoryPostProcessor:主体是 BeanFactory, 和 BeanPostProcessor:主体是 Bean,这两者都是 Spring 用来为使用者提供的扩展功能之一。

接下来为了更好的分析和了解使用后处理器,实现扩展功能,一起跟踪源码学习吧~


BeanFactoryPostProcessor

是什么

BeanFactoryPostProcessor 是一个接口,在里面只有一个方法定义:

  1. @FunctionalInterface
  2. publicinterfaceBeanFactoryPostProcessor{
  3.    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throwsBeansException;
  4. }

定义:是 Spring 对外提供可扩展的接口,能够在容器加载了所有 bean 信息( AbstractApplicationContext#obtainFreshBeanFactory 方法)之后, bean 实例化之前执行,用来修改 bean 的定义属性。

可以看到,方法参数是 ConfigurableListableBeanFactorybeanFactory ,说明我们可以通过引用该接口,在方法中实现逻辑,对容器中 bean 的定义(配置元数据)进行处理

同时,执行后处理器是有先后顺序的概念,我们可以通过设置 order 属性来控制它们的执行次序,前提是 BeanFactoryPostProcessor 实现了 Order 接口。

下面一起来看下它的如何使用,以及是如何进行注册和执行~


如何使用

官方例子:PropertyPlaceholderConfigurer

这个类是 Spring 容器里自带的后处理器,是用来替换占位符,填充属性到 bean 中。

像我们在 xml 文件中配置了属性值为 ${max.threads},能够通过它来找到 max.threads 在配置文件对应的值,然后将属性填充到 bean 中。

虽然在 Spring 5 中, PropertyPlaceholderConfigurer 已经打上了不建议使用的标志 @Deprecated,看了文件注释,提示我们去使用 Environment 来设置属性,但我觉得这个后处理器的思想是一样的,所以还是拿它作为例子进行熟悉。

先来看下它的继承体系:

10.jpg

~~忽略它被冷落的下划线标签~~

当 Spring 加载任何实现了 BeanFactoryPostProcessor 接口的 bean 配置时,都会在 bean 工厂载入所有 bean 的配置之后执行 postProcessBeanFactory 方法。

可以看到它引用了 BeanFactoryPostProcessor 接口,在 PropertyResourceConfigurer 父类中实现了 postProcessBeanFactory,在方法中依次调用了合并资源 mergedProps 方法,属性转换 convertProperties 方法和真正修改 beanFactory 中配置元数据的 processProperties(beanFactory,mergedProps) 方法。

因为通过在 PropertyPlaceholderConfigurer 的后处理方法 postProcessBeanFactory, BeanFactory在实例化任何 bean 之前获得配置信息,从而能够正确解析 bean 描述文件中的变量引用。

所以通过后处理器,我们能够对 beanFactory 中的 bean 配置信息在实例化前还有机会进行修改。

题外话:想到之前我遇到全半角空格的配置问题,程序认为全半角空格不是同一个字符,但肉眼却很难察觉,所以感觉可以在加载配置信息时,通过自定义一个后处理,在实例化之前,将全角空格转成半角空格,这样程序比较时都变成统一样式。所以后处理器提供的扩展功能可以让我们对想要处理的 bean 配置信息进行特定修改


使用自定义 BeanFactoryPostProcessor

实现的功能与书中的类似,例如之前西安奔驰汽车维权事件,如果相关网站想要屏蔽这奔驰这两个字,可以通过后处理器进行替换:

1. 配置文件 factory-post-processor.xml

  1. <beanid="carPostProcessor"class="context.CarBeanFactoryPostProcessor">
  2.    <propertyname="obscenties">
  3.        <!--set 属性-->
  4.        <set>
  5.            <value>奔驰</value>
  6.            <value>特斯拉</value>
  7.        </set>
  8.    </property>
  9. </bean>
  10. <beanid="car"class="base.factory.bean.Car">
  11.    <propertyname="price"value="10000"/>
  12.    <propertyname="brand"value="奔驰"/>
  13. </bean>

2. 后处理器 CarBeanFactoryPostProcessor

  1. publicclassCarBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{
  2.    /**
  3.     * 敏感词
  4.     */
  5.    privateSet<String> obscenties;
  6.    @Override
  7.    publicvoid postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throwsBeansException{
  8.        // 从 beanFactory 中获取 bean 名字列表
  9.        String[] beanNames = beanFactory.getBeanDefinitionNames();
  10.        for(String beanName : beanNames){
  11.            BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
  12.            StringValueResolver valueResolver = strVal ->{
  13.                if(isObscene(strVal))return"*****";
  14.                return strVal;
  15.            };
  16.            BeanDefinitionVisitor visitor =newBeanDefinitionVisitor(valueResolver);
  17.            // 这一步才是真正处理 bean 的配置信息
  18.            visitor.visitBeanDefinition(definition);
  19.        }
  20.    }
  21.    /**
  22.     * 判断 value 是否在敏感词列表中
  23.     * @param value 值
  24.     * @return      boolean
  25.     */
  26.    privateboolean isObscene(Object value){
  27.        String potentialObscenity = value.toString().toUpperCase();
  28.        returnthis.obscenties.contains(potentialObscenity);
  29.    }
  30. }

3. 启动

  1. publicclassBeanFactoryPostProcessorBootstrap{
  2.    publicstaticvoid main(String[] args){
  3.        ConfigurableApplicationContext context =newClassPathXmlApplicationContext("factory.bean/factory-post-processor.xml");
  4.        // 这两行其实可以不写,因为在 refresh() 方法中,调用了一个函数将后处理器执行了,具体请往下看~
  5.        BeanFactoryPostProcessor beanFactoryPostProcessor =(BeanFactoryPostProcessor) context.getBean("carPostProcessor");
  6.        beanFactoryPostProcessor.postProcessBeanFactory(context.getBeanFactory());
  7.        // 输出 :Car{maxSpeed=0, brand='*****', price=10000.0},敏感词被替换了
  8.        System.out.println(context.getBean("car"));
  9.    }
  10. }

通过上面的演示代码,新增一个自定义实现 BeanFactoryPostProcessor 的后处理器 CarBeanFactoryPostProcessor,在 postProcessBeanFactory 方法中进行逻辑处理,最后通过 visitor.visitBeanDefinition 修改配置信息。

查看输出结果,能发现宝马敏感词已经被屏蔽了,实现了后处理器的逻辑功能~


在哪注册

按照一般套路,后处理器需要有个地方进行注册,然后才能进行执行,通过代码分析,的确在 AbstractApplicationContext 中看到了 beanFactoryPostProcessors 数组列表,但往数组中添加后处理器的方法 addBeanFactoryPostProcessor 只在单元测试包调用了。

这让我很迷惑它到底是在哪里进行注册,直到我看到它的执行方法,原来我们定义的后处理器在 bean 信息加载时就放入注册表中,然后通过 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class,true,false) 方法获取后处理器列表遍历执行。

所以前面的 beanFactoryPostProcessors 数组列表,是让我们通过硬编码方法方式,手动添加进去,然后通过 context.refresh() 方法后,再执行硬编码的后处理器

例如下面这个例子

  1. publicclassHardCodeBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{
  2.    @Override
  3.    publicvoid postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throwsBeansException{
  4.        System.out.println("Hard Code BeanFactory Post Processor execute time");
  5.    }
  6. }
  7. // 硬编码 后处理器执行时间
  8. BeanFactoryPostProcessor hardCodeBeanFactoryPostProcessor =newHardCodeBeanFactoryPostProcessor();
  9. context.addBeanFactoryPostProcessor(hardCodeBeanFactoryPostProcessor);
  10. // 更新上下文
  11. context.refresh();
  12. // 输出:
  13. //Hard Code BeanFactory Post Processor execute time
  14. //Car{maxSpeed=0, brand='*****', price=10000.0}
  15. System.out.println(context.getBean("car"));

激活 BeanFactoryPostProcessor

看完了怎么使用后,我们来分析下 Spring 是如何识别和执行 BeanFactoryPostProcessor,入口方法:

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

实际上,委派了 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory,getBeanFactoryPostProcessors()) 代理进行执行。由于代码有点长,所以我画了一个流程图,可以结合流程图来分析代码:


相关文章
|
11月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
11月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
869 2
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
440 32
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
1565 0
|
安全 Java 数据库
Spring Boot 框架深入学习示例教程详解
本教程深入讲解Spring Boot框架,先介绍其基础概念与优势,如自动配置、独立运行等。通过搭建项目、配置数据库等步骤展示技术方案,并结合RESTful API开发实例帮助学习。内容涵盖环境搭建、核心组件应用(Spring MVC、Spring Data JPA、Spring Security)及示例项目——在线书店系统,助你掌握Spring Boot开发全流程。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
2191 3
|
Java Spring
Spring框架的学习与应用
总的来说,Spring框架是Java开发中的一把强大的工具。通过理解其核心概念,通过实践来学习和掌握,你可以充分利用Spring框架的强大功能,提高你的开发效率和代码质量。
386 20
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
1815 8
|
监控 安全 Java
Java 开发中基于 Spring Boot 3.2 框架集成 MQTT 5.0 协议实现消息推送与订阅功能的技术方案解析
本文介绍基于Spring Boot 3.2集成MQTT 5.0的消息推送与订阅技术方案,涵盖核心技术栈选型(Spring Boot、Eclipse Paho、HiveMQ)、项目搭建与配置、消息发布与订阅服务实现,以及在智能家居控制系统中的应用实例。同时,详细探讨了安全增强(TLS/SSL)、性能优化(异步处理与背压控制)、测试监控及生产环境部署方案,为构建高可用、高性能的消息通信系统提供全面指导。附资源下载链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)。
2851 0