BeanFactoryPostProcessor 和 BeanPostProcessor

news2024/9/25 1:21:01

BeanFactoryPostProcessor 和 BeanPostProcessor

    • 基本概念
    • BeanFactoryPostProcessor
      • 简单实践
      • BeanFactoryPostProcessor 典型应用
      • BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor
    • BeanPostProcessor
      • 简单实践
      • AOP 简单实践

基本概念

BeanFactoryPostProcessor 是一个后置处理器,在Spring中,后置处理器大致可以分为两类:

  • BeanFactoryPostProcessor
  • BeanPostProcessor
    前者主要是用来处理 BeanDefinition,即一个 Bean 的配置完全被加载了,已经生成了 BeanDefinition,但是还没有生成具体的 Bean,此时 BeanFactoryPostProcessor 会介入,介入之后可以对 BeanDefinition 进行处理;而 BeanPostProcessor 则是在一个具体的 Bean 生成之后,才会介入,对已经生成的 Bean 进行修改。

BeanFactoryPostProcessor

简单实践

如果在 Bean 创建的过程中,当 BeanDefinition 创建完毕,Bean 尚未创建的时候,想要去修改 Bean 的属性,那么就可以通过 BeanFactoryPostProcessor 来实现,直接定义 BeanFactoryPostProcessor 即可,定义好的 BeanFactoryPostProcessor 会针对当前项目中的所有 Bean 生效:

/**
 * 这个后置处理器定义好之后,会对所有的 Bean 生效
 */
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	/**
	 * 这个接口就是用来处理 beanDefinition 的,此时各个对象的 beanDefinition 都已经收集好,但是还没有创建 Bean 对象
	 * 相当于我们在正式创建 Bean 对象之前,先把 BeanDefintion 中的属性值给篡改了,这样,将来创建出来的 Bean 对象的值就是修改后的属
	 *
	 * @param beanFactory the bean factory used by the application context
	 * @throws BeansException
	 */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("BeanFactoryPostProcessor执行了...");
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames){
            BeanDefinition bd = beanFactory.getBeanDefinition(beanDefinitionName);
            //创建一个 BeanDefinitionVisitor 对象,这个对象可以用来访问并修改 BeanDefinition 中的属性值
 			//假设我一会给一个 bean 设置属性值为 NB,如果我发现 bean 的属性名是 hok,我就将之改为 NB
            BeanDefinitionVisitor beanDefinitionVisitor = new BeanDefinitionVisitor(strVal -> {
                if ("hok".equals(strVal)) {
                    return "NB";
                }
                //对于其他的属性值,原封不动返回即可
                return strVal;
            });
            //更新 BeanDefinition 对象,其实就是将 visitor 中的属性值,重新赋值到原本的 BeanDefinition 上去
            beanDefinitionVisitor.visitBeanDefinition(bd);
        }
    }
}

最后将这个 Bean 注册到 Spring 容器中即可,注册成功之后,他就会生效,不需要任何额外的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.example.demo.User" id="user">
        <property name="id" value="1" />
        <property name="name" value="hok" />
    </bean>
    <bean class="org.example.demo.CustomBeanFactoryPostProcessor" />
</beans>
public class User {
    private String name;
    private int id;

    public User() {
        System.out.println("user构造方法执行了...");
    }
    // set get...
}

启动类,使用 加载 xml 方式

public class App {
    public static void main(String[] args) {
        // 加载 xml 方式,默认的beanDefinition实现
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        User user = ctx.getBean(User.class);
        System.out.println("user = " + user);
    }
}

输出如下,发现 name 属性值确实被修改了
在这里插入图片描述
注意:上述对 BeanFactoryPostProcessor 的使用是基于 xml 方式来做的,如果想改成 java 配置的方式会发现属性改不了,原因这篇文章有写

BeanFactoryPostProcessor 典型应用

在整合 JDBC 的时候,经常要配置数据库密码 URL之类的,类似一个db.properties文件

db.username=root
db.password=root
db.url=jdbc:mysql:///$db$?serverTimezone=Asia/Shanghai

然后在 XML 文件中加载该配置并使用:

<context:property-placeholder location="classpath:db.properties"/>
<bean class="org.javaboy.demo.DataSource" id="dataSource">
 <property name="url" value="${db.url}"/>
 <property name="username" value="${db.username}"/>
 <property name="password" value="${db.password}"/>
</bean>

那么系统在读取配置文件的时候,首先读取到的 DataSource 中配置的各个属性的 value 就是 XML 文件中的 value,然后这个配置会经过一个BeanFactoryPostProcessor 后置处理器,在这个后置处理器中,会找到所有的 ${} 格式的 value,并将之替换为真正的值,后面再去创建 bean 的时候,用到的就是真实的值了。

context:property-placeholder 这种写法该如何理解?正常来说,我们向 Spring 容器中都是去注册 Bean 的,按理说配置应该是一个一个的 Bean 标签,但是!有时候对于某一些 Bean,我们希望能够有一些默认的配置,那么这些默认的配置就可以通过自定义标签来实现,
context:property-placeholder 这种写法实际上就是一个自定义标签,在这个标签中,会进行一些默认的 Bean 属性的配置,但是,一般来说,这些属性,如果想要自己去配置,也是可以的,比如 ignore-unresolvable

<context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/>

这个属性配置的含义表示,对于无法解析的 value,就不解析(默认情况下,如果遇到无法解析的 value,会抛出异常)。当然,如果我们明白了 context:property-placeholder 的本质(其实还是 Bean 的配置),那么也可以不使用这种自定义标签,而是直接自己手动去配置 Bean,像下面这样,等价于 context:property-placeholder

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
 <property name="location" value="classpath:db.properties"/>
 <property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>

而从类的继承关系上可以看出,PropertySourcesPlaceholderConfigurer 类实现了 BeanFactoryPostProcessor 接口并重写了接口中的 postProcessBeanFactory 方法:
在这里插入图片描述

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	//首先这个 propertySources 实际上就是我们的各种配置的 properties 资源。整体上分为两类:localProperties 和 environmentProperties
	//localProperties 是属于开发者自定义的各种 properties,例如我们前面写的 db.properties 就属于这一类
	//environmentProperties 是系统的环境变量信息,即 Spring 会读取到当前电脑的环境变量,并将之注入到 propertySources 里边来
    if (this.propertySources == null) {
		 //第一次加载的时候,propertySources 为 null,此时就要去初始化,并为之赋值
        this.propertySources = new MutablePropertySources();
        //如果环境变量对象不为 null,那么就从中读取 properties 出来
        if (this.environment != null) {
            final PropertyResolver propertyResolver = this.environment;
            if (this.ignoreUnresolvablePlaceholders && this.environment instanceof ConfigurableEnvironment) {
                ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment)this.environment;
                PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources());
                resolver.setIgnoreUnresolvableNestedPlaceholders(true);
                propertyResolver = resolver;
            }
			//将加载到的环境变量的信息存入到 propertySources 里边来
            this.propertySources.addLast(new PropertySource<Environment>("environmentProperties", this.environment) {
                @Nullable
                public String getProperty(String key) {
                    return ((PropertyResolver)propertyResolver).getProperty(key);
                }
            });
        }

        try {
        	//这里就是去加载本地的 properties 配置
            PropertySource<?> localPropertySource = new PropertiesPropertySource("localProperties", this.mergeProperties());
            //是否本地覆盖,如果为 true,就表示使用本地配置覆盖掉环境变量中的信息(如果环境变量中有 key 和本地配置中的 key 重复了
 			//默认情况下,localOverride 变量值为 false,即本地的 properties 配置,不会覆盖环境变量中的配置
            if (this.localOverride) {
           		 //将本地的添加到 list 集合的最前面,这样将来读取的时候,就优先读取到本地配置,相当于本地配置覆盖掉了环境变量配置
                this.propertySources.addFirst(localPropertySource);
            } else {
                this.propertySources.addLast(localPropertySource);
            }
        } catch (IOException var5) {
            throw new BeanInitializationException("Could not load properties", var5);
        }
    }
	// 接下来在 processProperties 方法进一步处理:
    this.processProperties(beanFactory, (ConfigurablePropertyResolver)(new PropertySourcesPropertyResolver(this.propertySources)));
    this.appliedPropertySources = this.propertySources;
}
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, final ConfigurablePropertyResolver propertyResolver) throws BeansException {
    propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
    propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
    propertyResolver.setValueSeparator(this.valueSeparator);
    //这个解析器就是核心了,在这里将 ${xxx} 转为具体的值
    StringValueResolver valueResolver = (strVal) -> {
        String resolved = this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal);
        if (this.trimValues) {
            resolved = resolved.trim();
        }
        return resolved.equals(this.nullValue) ? null : resolved;
    };
    //这个方法里边就是创建访问器,在访问器中,将 valueResolver 传入,对各个使用了占位符 ${xxx} 的 value 进行解析。
    this.doProcessProperties(beanFactoryToProcess, valueResolver);
}

BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor 本质上也是个BeanFactoryPostProcessor,从源码可以看到,多了个方法,说明这个类是为 BeanDefinitionRegistry 服务的,作为 BeanDefinitionRegistry 的后置处理器,它的执行时机比 BeanFactoryPostProcessor 稍早些

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

    default void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

BeanPostProcessor

BeanPostProcessor 接口中存在两个方法,一个大的前提,就是两个方法都是在 Bean 创建成功之后执行的,具体到方法上,postProcessBeforeInitialization 是在 bean 的初始化方法 afterPropertiesSetinit-method 方法之前执行,而 postProcessAfterInitialization 则是在这两个初始化方法之后执行。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

简单实践

来实践一下,接下来需要对 Book 对象修改其属性

public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if ("book".equals(beanName)) {
            Book book = (Book) bean;
            book.setId(111);
            return book;
        }
       return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("book".equals(beanName)) {
            Book book = (Book) bean;
            book.setName("金瓶梅");
            book.setPrice(666d);
            return book;
        }
        //如果是其他类型的 bean,则直接返回对象即可
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
}
public class Book {
  private String name;
  private Double price;
  private Integer id;
   // set get 方法
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.example.demo.Book" id="book" >
        <property name="id" value="0" />
        <property name="name" value="hok" />
        <property name="price" value="77d" />
    </bean>
    <bean class="org.example.demo.CustomBeanPostProcessor" />
</beans>

输出结果如下
在这里插入图片描述
注意:上面是使用 xml 形式,BeanPostProcessor 注解也是可以生效的

AOP 简单实践

Spring 中有非常多的功能是通过 BeanPostProcessor 来实现的,其中最为经典的是 AOP。AOP 需要先创建一个原始的 Bean,原始 Bean 创建成功之后,就是在 BeanPostProcessor 中,对原始 Bean 生成代理对象并返回。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2077929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百度翻译与TOP3在线翻译伙伴:2024年的黄金组合

在这个信息丰富的时代&#xff0c;语言帮助人们跨越地域界限进行交流。随着全球化的发展&#xff0c;高效的在线翻译工具变得越来越重要&#xff0c;它能帮我们更好地了解世界和不同的文化。今天&#xff0c;我们就来看看百度翻译和它的三个新对手之间的比较&#xff0c;一起找…

redis作为缓存,mysql的数据如何与redis同步

先介绍自己的业务背景&#xff0c;是一致性要求高的 还是 允许延迟一致&#xff0c;因为两者的回答不一样。 双写一致性 双写一致性:当修改了数据库的数据也要同时更新缓存的数据&#xff0c;缓存和数据库的数据要保持一致。 读操作:缓存命中&#xff0c;直接返回;缓存未命中…

动⼿学深度学习

大家如果想获取这个PDF可以关注&#xff0c;收藏点赞并私信我&#xff0c;我把这个PDF文档发给感兴趣的朋友 目录 预备知识 预备知识内容 线性神经网络 线性回归 基本元素 矢量化加速 正态分布与平方损失 从线性回归到深度网络 小结与练习 线性回归的实现 从零开始实现…

safari扩展程序开发

文章目录 safari_web_extensions开发扩展扩展有3个主要部分&#xff1a;使用 WebExtension APIruntime local debugSafari中允许运行 未签名的扩展install extensionupdate extension publish safari的extension文档不是很好&#xff0c;建议参考mozilla文档 https://developer…

【Python零基础】类的概念

文章目录 前言一、创建并使用类二、使用类和实例三、继承四、导入类五、Python标准库六、类的编码风格总结 前言 面向对象编程是现在使用最多的软件编写方法之一。面向对象中最核心的就是类(class)&#xff0c;类用来描述现实世界的事物和情景&#xff0c;我们基于这些类来创建…

信号的产生

文章目录 2 信号的产生2.1 键盘组合键2.2 命令和函数调用2.2.1 kill命令2.2.2 raise()函数2.2.3 abort()函数 2.3 硬件异常2.3.1 除0异常2.3.2 空指针异常2.3.3 OS如何感知这些异常--除0异常2.3.4 OS如何感知这些异常--空指针异常 2.4 软件条件2.4.1 13&#xff09;SIGPIPE信号…

yolov5关键点检测-实现溺水检测与警报提示(代码+原理)

基于YOLOv5的关键点检测应用于溺水检测与警报提示是一种结合深度学习与计算机视觉技术的安全监控解决方案。该项目通常会利用YOLOv5强大的实时目标检测能力&#xff0c;并通过扩展或修改网络结构以支持人体关键点检测&#xff0c;来识别游泳池或其他水域中人们的行为姿态。 项…

12-使用gateway作为微服务网关

本文介绍spring gateway的使用&#xff0c;包括配置文件和使用java代码配置&#xff0c;让大家了解spring gateway的用法。如果不了解什么是微服务网关&#xff0c;就先查查资料&#xff0c;网关相对来说是比较重要的微服务组件。 0、环境 springboot 2.4.2springcloud gatew…

游戏开发设计模式之装饰模式

目录 装饰模式在游戏开发中的具体应用案例是什么&#xff1f; 如何在Unity中实现装饰模式以动态扩展游戏对象的功能&#xff1f; 装饰模式与其他设计模式&#xff08;如适配器模式、代理模式&#xff09;相比&#xff0c;有哪些优势和劣势&#xff1f; 优势 劣势 与适配器…

唯众2024年高职人工智能实训室方案解读

随着人工智能&#xff08;AI&#xff09;技术在全球范围内的快速发展&#xff0c;越来越多的职业技术学院开始重视AI相关专业的建设和实训室的搭建。作为在人工智能教育领域有着丰富经验的企业&#xff0c;唯众针对2024年的市场需求&#xff0c;推出了一套全面的人工智能实训室…

软件设计师教程(第5版)第8章 算法设计与分析(更新中)

8.1 算法设计与分析的基本概念P416 8.1.1 算法P416 【算法】是对特定问题求解步骤的一种描述&#xff0c;它是指令的有限序列&#xff0c;其中每一条指令表示一个或多个操作。P416 一个算法还具有下列5个重要特性&#xff1a;【有穷】性、【确定】性、【可行】性、【输入】、…

Games101学习 - 线性代数综述

1. 叉积矩阵形式 叉乘矩阵形式通常在物理模拟中有运用&#xff0c;处理四元数旋转也类似这样的形式。 // 定义两个向量 A 和 B FVector A(1.0f, 2.0f, 3.0f); FVector B(4.0f, 5.0f, 6.0f);// 计算叉积 FVector CrossProduct FVector::CrossProduct(A, B);if (GEngine) {GEn…

CVPR2024满分论文:基于可变形三维高斯的高质量单目动态重建方法

一、摘要 隐式神经表征为动态场景的重建和渲染开辟了新的途径。然而&#xff0c;尖端的动态神经渲染方法严重依赖这些隐式表征&#xff0c;它们常常难以捕捉场景中物体的复杂细节。此外&#xff0c;隐式方法通常难以实现动态场景的实时渲染&#xff0c;限制了它们在多种任务中的…

Excel公式与图表自动化:在Python中操作Excel公式并自动化生成图表

目录 一、Python操作Excel公式 1.1 读取Excel文件 1.2 识别和处理公式 1.3 批量处理公式 二、自动化生成图表 2.1 使用pandas和matplotlib生成图表 2.2 使用xlwings在Excel中直接生成图表 2.3 自定义图表样式 2.4 自动化生成复杂图表 三、总结 在数据分析和自动化办公…

VMware Workstation Pro for Personal Use (For Windows) 17.0.0

VMware Workstation Pro for Personal Use (For Windows) 17.0.0 弄了半天终于找到下载地址了 现在VMware被博通&#xff08;broadcom&#xff09;收购且宣布了17.5版本的VMware Workstation Pro对个人用户免费许可使用。由于现在官网的下载方式有改变&#xff0c;故贴出来一…

【数学分析笔记】第2章第4节收敛准则(4)

2.数列极限 2.4 收敛准则 上节课举了一个例子 a N 1 1 2 p 1 3 p . . . 1 n p a_{N}1\frac{1}{2^{p}}\frac{1}{3^{p}}...\frac{1}{n^{p}} aN​12p1​3p1​...np1​ p > 1 p>1 p>1&#xff0c; { a n } \{a_{n}\} {an​}收敛 0 < p ≤ 1 0<p\le 1 0<p≤…

OpenStack——keystone认证服务

1、作用 认证授权 服务目录 2、组件 keystone-server keystone-DB 3、架构 ①组成 用户认证流程&#xff1a; 1.Horizon为用户提供界面; 2.用户输入用户名密码&#xff0c;有Horizon转发至Keystone做认证授权 ; 3.如果认证鉴权成功&#xff0c;会给用户发放一个临时的unscope…

AIGC:Flux.1 NF4 使用 diffusers 推理

背景介绍 Flux 参数量很大 (包括 ext encoder2, autoencoder, 以及 diffusion model) , 使用Diffusers推理,显存占用 30GB。 有大佬做了 NF4 版本,效果依旧能打。所以本文使用 diffusers 推理 NF4版本的 Flux.1 本文重点 1:flux.1-dev-nf4 国内镜像加速下载 2:依赖环境…

error: undefined reference to `__imp__ZN11QSerialPortC1EP7QObject‘

问题 在qt console程序里使用QSerialPot, 在.pro文件里添加了serialport&#xff0c;在main.cpp里也包含了QtSerialPort/QSerialPort&#xff0c;但是编译报错如下&#xff1a; 原因 serialport模块加错了位置&#xff0c;应该添加到QT后面&#xff0c;而实际添加到CONFIG后…

mybatis-plus中Swagger 模式和Kotlin 模式是什么?

在 MyBatis-Plus 中&#xff0c;Swagger 模式和 Kotlin 模式是为了支持特定技术栈和开发需求的两种配置选项。它们分别针对 API 文档生成和 Kotlin 语言提供了更好的支持和集成。 Swagger 模式 Swagger 模式主要用于生成 API 文档。在 MyBatis-Plus 中启用 Swagger 模式后&am…