我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spring BeanPostProcessor(后置处理器)的用法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spring BeanPostProcessor(后置处理器)的用法

为了弄清楚Spring框架,我们需要分别弄清楚相关核心接口的作用,本文来介绍下BeanPostProcessor接口

BeanPostProcessor

该接口我们也叫后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。接口的源码如下


public interface BeanPostProcessor { 
 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
方法 说明
postProcessBeforeInitialization 实例化、依赖注入完毕,
在调用显示的初始化之前完成一些定制的初始化任务
postProcessAfterInitialization 实例化、依赖注入、初始化完毕时执行

一、自定义后置处理器演示

1.自定义处理器


package com.dpb.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor{
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("初始化 before--实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("初始化 after...实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
}

注意:接口中两个方法不能返回null,如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象,因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中

2.Pojo类


public class User {
 private int id;
 
 private String name;
 
 private String beanName;
 
 public User(){
  System.out.println("User 被实例化");
 }
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  System.out.println("设置:"+name);
  this.name = name;
 }
 public String getBeanName() {
  return beanName;
 }
 public void setBeanName(String beanName) {
  this.beanName = beanName;
 }
 
 public void start(){
  System.out.println("User 中自定义的初始化方法");
 }
}

3.配置文件注册


<?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 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.dpb.pojo.User" id="user" init-method="start">
  <property name="name" value="波波烤鸭" />
 </bean>
 
 <!-- 注册处理器 -->
 <bean class="com.dpb.processor.MyBeanPostProcessor"></bean>
</beans>

4.测试


 @Test
public void test() {
 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
 User user = ac.getBean(User.class);
 System.out.println(user);
}

输出结果

User 被实例化
设置:波波烤鸭
初始化 before--实例化的bean对象:com.dpb.pojo.User@65e2dbf3 user
User 中自定义的初始化方法
初始化 after...实例化的bean对象:com.dpb.pojo.User@65e2dbf3 user
com.dpb.pojo.User@65e2dbf3 

通过输出语句我们也能看到postProcessBeforeInitialization方法的输出语句是在Bean实例化及属性注入后执行的,且在自定义的初始化方法之前执行(通过init-method指定)。而postProcessAfterInitialization方法是在自定义初始化方法执行之后执行的。

注意!!!

BeanFactory和ApplicationContext两个容器对待bean的后置处理器稍微有些不同。ApplicationContext容器会自动检测Spring配置文件中那些bean所对应的Java类实现了BeanPostProcessor接口,并自动把它们注册为后置处理器。在创建bean过程中调用它们,所以部署一个后置处理器跟普通的bean没有什么太大区别。

BeanFactory容器注册bean后置处理器时必须通过代码显示的注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法



void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

测试代码如下


@Test
public void test2() {
 //ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
 XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
 // 显示添加后置处理器
 bf.addBeanPostProcessor(bf.getBean(MyBeanPostProcessor.class));
 User user = bf.getBean(User.class);
 System.out.println(user);
}

二、多个后置处理器

我们可以在Spring配置文件中添加多个BeanPostProcessor(后置处理器)接口实现类,在默认情况下Spring容器会根据后置处理器的定义顺序来依次调用。


public class MyBeanPostProcessor implements BeanPostProcessor{
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A before--实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A after...实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
}

public class MyBeanPostProcessor2 implements BeanPostProcessor{
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B before--实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B after...实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
}

配置文件注册


<?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 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.dpb.pojo.User" id="user" init-method="start">
  <property name="name" value="波波烤鸭" />
 </bean>
 
 <!-- 注册处理器 -->
 <bean class="com.dpb.processor.MyBeanPostProcessor"/>
 <bean class="com.dpb.processor.MyBeanPostProcessor2"/>
</beans>

测试结果

User 被实例化
设置:波波烤鸭
A before--实例化的bean对象:com.dpb.pojo.User@7fac631b user
B before--实例化的bean对象:com.dpb.pojo.User@7fac631b user
User 中自定义的初始化方法
A after...实例化的bean对象:com.dpb.pojo.User@7fac631b user
B after...实例化的bean对象:com.dpb.pojo.User@7fac631b user
com.dpb.pojo.User@7fac631b

三、显示指定顺序

在Spring机制中可以指定后置处理器调用顺序,通过让BeanPostProcessor接口实现类实现Ordered接口getOrder方法,该方法返回一整数,默认值为 0,优先级最高,值越大优先级越低


public class MyBeanPostProcessor implements BeanPostProcessor,Ordered{
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A before--实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A after...实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 @Override
 public int getOrder() {
  // TODO Auto-generated method stub
  return 10;
 }
}

public class MyBeanPostProcessor2 implements BeanPostProcessor,Ordered{
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B before--实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B after...实例化的bean对象:"+bean+"\t"+beanName);
  // 可以根据beanName不同执行不同的处理操作
  return bean;
 }
 @Override
 public int getOrder() {
  // TODO Auto-generated method stub
  return 2;
 }
}

测试输出结果

User 被实例化
设置:波波烤鸭
B before--实例化的bean对象:com.dpb.pojo.User@7fac631b user
A before--实例化的bean对象:com.dpb.pojo.User@7fac631b user
User 中自定义的初始化方法
B after...实例化的bean对象:com.dpb.pojo.User@7fac631b user
A after...实例化的bean对象:com.dpb.pojo.User@7fac631b user
com.dpb.pojo.User@7fac631b

数值越大的优先级越低,所以A的输出就在后面了。

对BeanPostProcessor接口的理解

今天想起来写一篇,是因为自己纠正了对BeanPostProcessor接口的理解误区,写文章往往都是源于这种豁然开朗的灵感。不过今天又是孤陋寡闻的一天呢,一个知识点理解错了这么长时间居然都不自知。

Bean的生命周期应该都很清楚,先贴这张图

这张Bean生命周期顺序图里大部分环节都是比较好理解的,比如setBeanName和setBeanFactory包括setApplicationContext,都是实现了对应的接口,就可以在实例化这个Bean的时候为这个Bean设置BeanName,或者注入BeanFactory和ApplicationContext,可以用于获取IOC容器中的其他Bean等。但是实现了BeanPostProcessor接口不能按这个逻辑去理解,

先看一下实现BeanPostProcessor接口后的重写哪两个方法:

之前我一直是按这个理解逻辑去理解实现了BeanPostProcessor接口,理解误区是:MyBeanPost这个类实现了BeanPostProcessor接口,实例化的MyBeanPost的时候就会去调用该类里重写的postProcessBeforeInitialization()和postProcessAfterInitialization()方法,这两个方法里拿到的beanName就是@Component里定义的"myBeanPost",Object类型的bean就是MyBeanPost对象,然后实例化MyBeanPost对象前后去在这两个方法里做点什么。

之前一直是这么理解的,其实一直是有疑惑的,因为按这么理解,postProcessBeforeInitialization()方法能做的在自定义的@PostConstruct方法里也能做,那这两个就区分不开了。虽然有疑惑但自己也没有去试过,直到今天项目开发的时候真的想用postProcessAfterInitialization()方法去在初始化完一个Bean的时候注入点东西的时候,一试傻眼了。

直接贴上图那种写法时的启动日志:

如果按我之前那么理解,这里应该只打印出"myBeanPost执行了postProcessBeforeInitialization"和"myBeanPost执行了postProcessAfterInitialization"才对啊,居然打印出了这么多,而且我全局搜了一下,偏偏没有beanName是"myBeanPost"的日志记录。这个时候我才知道之前我一直理解错了,于是重视起来开始找原因。

Spring的源码一顿翻之后找到了Spring初始化Bean的一段代码:

这里的invokeInitMethods()就是反射调用我们自定义的初始化方法,即顺序图中的第八步,可以清楚的看到applyBeanPostProcessorsBeforeInitialization()方法在前,applyBeanPostProcessorsAfterInitialization()方法在后,这似乎也和顺序图中执行postProcessBeforeInitialization()在执行自定义初始化方法前,执行postProcessAfterInitialization()在后对应上了,继续点进去看,

先看applyBeanPostProcessorsBeforeInitialization()方法

需要注意这里existingBean参数是正在实例化的Bean,这里的getBeanPostProcessors()方法是去拿所有实现了BeanPostProcessor接口的实现类的Bean,然后再调用BeanPostProcessor接口实现类的postProcessBeforeInitialization()方法。

看到这里就推翻了我之前的理解了,原来一个类实现了BeanPostProcessor接口,那重写的两个方法不是实例化该类的时候调用的,而是容器实例化其他Bean的时候调用的,容器会找出当前容器中所有实现了BeanPostProcessor接口实现类对象,然后一个遍历一个一个调用,这也是为什么上图打印出来的日志会有这么多BeanName的日志记录。也就是说如果容器需要实例化N个Bean,同时容器中已有M个BeanPostProcessor接口实现类对象,那BeanPostProcessor接口的那两个方法就会被调用N*M次,虽然是在不同的实现类中调用的。

applyBeanPostProcessorsAfterInitialization()同理:

一样的,总结一下,对于BeanPostProcessor接口的理解的理解应该是这样:

BeanPostProcessor接口有两个方法,分别为Bean容器中每个对象被实例化前和实例化后调用,即交给Bean容器管理的所有对象,比如打了@Component,@RestController等注解的类,程序启动时就会去实例化并放到Bean容器中的这些类。每次实例化一个Bean都会调用BeanPostProcessor接口重写方法,所有那两个方法是被多次调用的。应该在那两个方法中很据BeanName拿到自己想处理的Bean实例,再去做对应处理。

文章开头也提到,日志打印了这么多,偏偏没有beanName是"myBeanPost"的日志记录,也就是说,BeanPostProcessor接口的实现类明明也打了@Component注解,可是为啥在自己的重写方法中打印不出来beanName是自己的日志记录呢?这是正常而且必然的。因为虽然MyBeanPost也打了@Component,程序启动时也会去实例化这个Bean,但是实例化它的时候,getBeanPostProcessors()方法是拿不到MyBeanPost自己这个Bean的。还没实例化完呢怎么放进Bean容器中,没放进去当然拿不到了,自然也不会去执行这个Bean里重写的方法了。

不过如果另外写一个BeanPostProcessor接口的实现类就不一定了,如果实例化MyBeanPost的时候另一个BeanPostProcessor实现类已经被实例化好了放进Bean容器中了,getBeanPostProcessors()就能拿到,然后在另一个BeanPostProcessor实现类里重写的方法里打印出beanName为"myBeanPost"的日志记录。反正,BeanPostProcessor实现类不能在自己重写的方法中不能拿到自己的Bean实例。

所以BeanPostProcessor接口的正确用法应该是写一个MyBeanPostProcessor实现类,意思是自定义的Bean处理类,然后在这个自定义的Bean处理类中根据beanName去筛选拿到Bean容器中的其他Bean,做一些实例化这些Bean之前之后的逻辑。例如这样:

要注意重写的方法默认是返回null的,这里要返回处理后的bean,如果返回null就会是处理之前的bean,这个逻辑在applyBeanPostProcessorsBeforeInitialization()方法和applyBeanPostProcessorsAfterInitialization()方法里,

之前贴的图里有,再贴一遍:

current即自定义处理之后的,如果是null,还是返回result。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Spring BeanPostProcessor(后置处理器)的用法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring中Bean后置处理器如何使用

这篇文章主要讲解了“Spring中Bean后置处理器如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring中Bean后置处理器如何使用”吧!一、BeanPostProcessor
2023-07-02

使用Spring MVC实现统一异常处理的方法

这篇文章将为大家详细讲解有关使用Spring MVC实现统一异常处理的方法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1 描述 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业
2023-05-31

亚马逊云服务器到期后的处理方法

1.续费云服务器如果您对亚马逊云服务器(AWS)的服务感到满意,并且希望继续使用它,您可以选择续费您的云服务器。AWS提供了灵活的计费选项,您可以根据您的需求选择按小时、按月或按年计费。您可以在AWS管理控制台上轻松地续费您的云服务器,并根据您的预算和需求进行调整。2.迁移至其他云服务提供商如果您对AWS的服务不再满意,或者您希望尝试其他云服务提供商的服务,您可以选择迁移您的应用程序和数据至其他云服务...
2023-10-27

阿里云服务器被格式化后的处理方法

文章简介:当你在使用阿里云服务器的过程中,不幸发生了服务器被格式化的事件,你可能会感到惊慌失措和不知所措。然而,不必过于担忧,因为处理这种情况并不是那么困难。本文将为你提供一些实用的解决方案,帮助你在最短的时间内恢复服务器。文章正文:在阿里云服务器被格式化后,你需要首先做一些基本的操作以确保服务器的数据安全。首先,你需
阿里云服务器被格式化后的处理方法
2024-01-25

阿里云服务器漏洞修复后的处理方法

本文将介绍当阿里云服务器发现漏洞后,如何进行修复并处理后续问题的方法。首先,我们将解释漏洞修复的重要性,并列举常见的漏洞类型。然后,我们将探讨如何通过合理的配置和更新来提高服务器的安全性。最后,我们将介绍一些实际案例,以帮助读者更好地理解如何应对服务器漏洞。1.漏洞修复的重要性漏洞修复是保护服务器安全的关键步骤之
阿里云服务器漏洞修复后的处理方法
2023-12-26

Spring中拦截器的原理与使用方法

这篇文章主要讲解了“Spring中拦截器的原理与使用方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring中拦截器的原理与使用方法”吧!1.Spring中的拦截器在web开发中,拦截
2023-07-02

C#预处理器指令的用法

本文主要介绍了C#预处理器指令的用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-16

css预处理器的使用方法

本篇内容主要讲解“css预处理器的使用方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“css预处理器的使用方法”吧!1、什么是css预处理器CSS预处理器是一种专门的编程语言,用来为CSS增加
2023-06-20

threejs后期处理的基本使用方法以及如何加特效

threejs后期处理的基本使用方法以及如何加特效,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。前言后期处理:简单的说就是先渲染一张图存起来,在这张图上面"添油加醋",处理
2023-06-28

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录