Springboot扩展点之BeanPostProcessor

news2024/10/1 21:44:45

前言

        Springboot(Spring)的扩展点其实有很多,但是都有一个共同点,都是围绕着Bean和BeanFactory(容器)展开的,其实这也很好理解,Spring的核心是控制反转、依赖注入、面向切面编程,再抛开所有的枝枝节节,你发现了什么?Spring提供了一个容器,来管理Bean,整个生态好像是都围绕这个展开。研究源码意义,一方面是在于技术本身,另一方面也在于理解接受其中的思想。

        没有目的的乱走总是会迷路,有了目标就不一样了,所以这篇文章是围绕以下几个问题展开的,这也是我想和大家分享的内容:(如果你和我的疑问一样,关注,收藏+点赞,不迷路哦)

1、BeanPostProcessor接口的功能特性是什么样的?

2、BeanPostProcessor接口怎么实现扩展?

3、BeanPostProcessor接口的实现类的工作原理是什么?

4、BeanPostProcessor接口的应用场景有哪些?


功能特性

1、BeanPostProcessor是Bean级别的扩展接口,在Spring管理的Bean实例化完成后,预留了两种扩展点;

2、这两处扩展的实现方式就是实现BeanPostProcessor接口,并将实现类注册到Spring容器中;

3、两种扩展点分别是BeanPostProcessor接口的postProcessBeforeInitialization方法和postProcessAfterInitialization方法;

4、postProcessBeforeInitialization方法的执行时机是在Spring管理的Bean实例化、属性注入完成后,InitializingBean#afterPropertiesSet方法以及自定义的初始化方法之前;

5、postProcessAfterInitialization方法的执行时机是在InitializingBean#afterPropertiesSet方法以及自定义的初始化方法之前之后;

6、BeanPostProcessor接口的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法,在在Spring管理的每个bean初始化后都会执行到;


实现方式

1、定义一个实体类Dog,并实现InitializingBean接口,并且实现afterPropertiesSet()。其中afterPropertiesSet()和init()是为了演示BeanPostProcessor接口的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法的执行时机;

@Getter
@Setter
@Slf4j
public class Dog implements InitializingBean {
    private String name = "旺财";
    private String color = "黑色";
    public Dog() {
        log.info("---dog的无参构造方法被执行");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("---afterPropertiesSet被执行");
    }
    public void init() {
        log.info("---initMethod被执行");
    }
}

        把Dog类注册到Spring容器中,并设置了Bean实例化后的初始化方法;

@Configuration
public class SpringConfig {
    @Bean(initMethod = "init")
    public Dog dog(){
        Dog dog = new Dog();
        return dog;
    }
}

2、定义MyBeanPostProcessor,并且实现BeanPostProcessor接口;(这里类的命名和方法内逻辑仅是为了演示需要,实际开发中需要以实际逻辑来替换掉演示内容)

@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("dog")) {
            log.info("postProcessBeforeInitialization---" + beanName);
            //如果特定的bean实例化完成后,还未执行InitializingBean.afterPropertiesSet()方法之前,有一些其他操作,可以在这里实现
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("dog")) {
            log.info("postProcessAfterInitialization---" + beanName);
            //如果特定的bean实例化完成,InitializingBean.afterPropertiesSet()方法执行后,有一些其他操作,可以在这里实现
        }
        return bean;
    }
}

3、编写单元测试,来验证结果;

@SpringBootTest
@Slf4j
public class FanfuApplicationTests {
   @Test
    public void test3(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
        Dog dog = ((Dog) context.getBean("dog"));
        log.info(dog.getName());
    }
}

        结论:从单元测试的执行结果来看,验证了Spring的扩展点BeanPostProcessor的执行时机,即postProcessBeforeInitialization方法的执行时机是在Spring管理的Bean实例化、属性注入完成后,InitializingBean#afterPropertiesSet方法以及自定义的初始化方法之前;postProcessAfterInitialization方法的执行时机是在InitializingBean#afterPropertiesSet方法以及自定义的初始化方法之前之后;


        以上演示了BeanPostProcessor作为Springboot的扩展点之一的实现方式和执行时机,下面从示例入手,来了解一下其基本的工作原理,正所谓知其然还要知其所以然嘛。

工作原理

        BeanPostProcessor的工作原理的关键其实就是两点,第一,BeanPostProcessor的实现类是什么时候被注册的?第二,BeanPostProcessor的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法是如何被执行的?

注册时机

1、BeanPostProcessor中的两个扩展方法中,postProcessBeforeInitialization方法是先被执行的,即Bean实例化和属性注入完成之后,通过实现方式示例代码的Debug,找到了BeanPostProcessor接口的实现类到Spring容器中的入口,即org.springframework.context.support.AbstractApplicationContext#refresh--->registerBeanPostProcessors

2、进入到AbstractApplicationContext#registerBeanPostProcessors方法内,会发现这段代码很干净,即依赖于PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法;

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
   PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}

3、进入到PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法又是另一番洞天:第一步,获取所有实现BeanPostProcessor接口的实现类的名称,实现方式示例中的MyBeanPostProcessors就在其中;

第二步,提前注册BeanPostProcessorChecker,主要用途是用于Bean创建过程中的日志信息打印记录;

第三步,就是把所有的BeanPostProcessor接口的实现类,按照是否实现PriorityOrdered接口、是否实现Ordered接口、其他,分为三组;

最后,下面的内容很长,不过很简单,即按第二步分成的三类,依次注册,具体的顺序是实现PriorityOrdered接口BeanPostProcessor接口的实现类、实现实现Ordered接口BeanPostProcessor接口的实现类、其他的BeanPostProcessor接口的实现类;

        总结,BeanPostProcessor的注册时机是在Spring容器启动过程中,即BeanFactoryPostProcessor扩展点的逻辑执行完成后,紧接着就开始了BeanPostProcessor的注册,其具体的注册逻辑在PostProcessorRegistrationDelegate#registerBeanPostProcessors()。

 

执行时机

        从实现方式的示例中验证得知,BeanPostProcessor接口的实现类的执行时机是在Spring管理的Bean实例化、属性注入完成后,那么找到Dog类的实例化入口,那么离BeanPostProcessor接口的实现类的执行时机也就不远了。

1、通过Debug调试,注册到Spring容器中的Dog类的实例化入口,即org.springframework.context.support.AbstractApplicationContext#refresh--->finishBeanFactoryInitialization();

2、进入到finishBeanFactoryInitialization(),发现实现方式示例中的Dog类是在DefaultListableBeanFactory#preInstantiateSingletons--->getBean()中实例化完成的。这里大致介绍一下getBean()业务逻辑:当获取某一个bean时,先查询缓存确定是否存在,若存在,则直接返回,若不存在,则开始创建Bean,若Bean内依赖了另外一个Bean,则是上述过程的一个递归。

3、从getBean方法进入后,主要过程是AbstractBeanFactory#doGetBean-->AbstractBeanFactory#createBean-->AbstractAutowireCapableBeanFactory#doCreateBean-->AbstractAutowireCapableBeanFactory#createBeanInstance,至此完成了Bean的实例化和属性注入。到这要打起精神了,要找的BeanPostProcessor接口的实现类的执行时机马上就到。果然在AbstractAutowireCapableBeanFactory#doCreateBean方法中,Dog类实例化完后,又调用initializeBean()进行bean的初始化操作,而BeanPostProcessor接口的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法的执行时机分别是在Bean的初始化方法执行前后触发,那么这个方法大概率就是BeanPostProcessor接口的实现类的执行时机的入口了。

4、进入到initializeBean()一看,判断的果然没错,先执行BeanPostProcessor接口实现类的postProcessBeforeInitialization方法,接着如果bean实现了InitializingBean或者自定义了initMethod,就会在这里执行InitializingBean#afterPropertiesSet和initMethod方法,最后会执行执行BeanPostProcessor接口实现类的postProcessAfterInitialization方法;

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }else {
      invokeAwareMethods(beanName, bean);
   }
   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
       //执行BeanPostProcessor接口实现类的postProcessBeforeInitialization方法
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }
   try {
       //如果bean实现了InitializingBean或者自定义了initMethod,
       //会在这里执行InitializingBean#afterPropertiesSet和initMethod方法
      invokeInitMethods(beanName, wrappedBean, mbd);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   if (mbd == null || !mbd.isSynthetic()) {
       //执行BeanPostProcessor接口实现类的postProcessAfterInitialization方法
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
   return wrappedBean;
}

5、下面分别再进入到applyBeanPostProcessorsBeforeInitialization()、invokeInitMethods()、applyBeanPostProcessorsAfterInitialization(),看看具体是怎么实现的。先来看applyBeanPostProcessorsBeforeInitialization():如果仔细研究过之前的Springboot扩展点之BeanFactoryPostProcessor 、Springboot扩展点之BeanDefinitionRegistryPostProcessor 、Springboot扩展点之ApplicationContextInitializer这几篇文章,那么对这个方法的套路就再熟悉不过了:先获取到所有注册到Spring容器中BeanPostProcessor接口的实现类,然后再遍历执行触发方法,就这么朴实无华。

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
      throws BeansException {
   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessBeforeInitialization(result, beanName);
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}

6、再来看一下,AbstractAutowireCapableBeanFactory#invokeInitMethods,逻辑也是很清晰,先判断是否实现了InitializingBean接口,如果实现了InitializingBean接口,就会触发执行afterPropertiesSet(),然后判断有没有自定义initMethod方法,如果有,则在这里开始执行;

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
      throws Throwable {
    //判断是否实现了InitializingBean接口
   boolean isInitializingBean = (bean instanceof InitializingBean);
   if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
      if (logger.isTraceEnabled()) {
         logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
      }
      if (System.getSecurityManager() != null) {
         try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
               ((InitializingBean) bean).afterPropertiesSet();
               return null;
            }, getAccessControlContext());
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }else {
          //如果实现了InitializingBean接口,就会重写afterPropertiesSet(),这里就会触发执行
         ((InitializingBean) bean).afterPropertiesSet();
      }
   }
   if (mbd != null && bean.getClass() != NullBean.class) {
       //判断有没有自定义initMethod方法,如果有,则在这里开始执行;
      String initMethodName = mbd.getInitMethodName();
      if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
         invokeCustomInitMethod(beanName, bean, mbd);
      }
   }
}

7、最后来看一下applyBeanPostProcessorsAfterInitialization(),前面applyBeanPostProcessorsBeforeInitialization()看懂了,这里就没有必要分析了,如出一辙,熟悉配方,熟悉的味道。

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
      throws BeansException {

   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessAfterInitialization(result, beanName);
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}

        至此,Springboot扩展点BeanPostProcessor的工作原理分析完了,归根结底就是两点,第一,在Spring容器初始化的过程中,完成扩展点的注册;第二,在Spring中Bean完成实例化和属性注入后,开始触发已注册的扩展点的扩展动作。内容很长,但是逻辑简单,希望阅读到这篇文章的小伙伴能够有耐心看完,因为我在研究清楚整个过程后,我是感觉获益良多的,希望你也是。


应用场景

        其实了解了BeanPostProcessor的功能特性、实现方式和工作原理,在遇到类似的业务需求的时候都可以应用这个扩展点,这里举两个我想到的应用场景:

处理自定义注解

        在程序中我们可以自定义注解并标到相应的类上,当个类注册到Spring容器中,并实例化完成后,希望触发自定义注解对应的一些其他操作的时候,就可以通过BeanPostProcessor来实现。

参数校验

        前面有两篇文章优雅的Springboot参数校验(一) 、优雅的Springboot参数校验(二) 和大家分享了参数校验具体实现方式,其核心原理正是用到了BeanPostProcessor扩展点,具体的实现类是org.springframework.validation.beanvalidation.BeanValidationPostProcessor

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

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

相关文章

西湖论剑 2023 比赛复现

WEB real_ez_node 在 route/index.js 中&#xff1a; router.post(/copy,(req,res)>{res.setHeader(Content-type,text/html;charsetutf-8)var ip req.connection.remoteAddress;console.log(ip);var obj {msg: ,}if (!ip.includes(127.0.0.1)) {obj.msg"only for…

【设计模式之美 设计原则与思想:面向对象】13丨实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?

面向对象分析&#xff08;OOA&#xff09;、面向对象设计&#xff08;OOD&#xff09;、面向对象编程&#xff08;OOP&#xff09;&#xff0c;是面向对象开发的三个主要环节。在前面的章节中&#xff0c;我对三者的讲解比较偏理论、偏概括性&#xff0c;目的是让你先有一个宏观…

电脑重装系统注册表恢复方法

​今天讲关于大家的电脑在遇到一些故障的时候&#xff0c;以及电脑用久了之后会卡顿&#xff0c;那么这时候大家一般都会给电脑重装系统。重装系统之后却发现自己电脑里的注册表不见了&#xff0c;重装系统后怎么恢复注册表?小编就带着大家一起学习重装系统注册表恢复到底是怎…

【博客615】通过systemd设置cgroup来限制服务资源争抢

通过systemd设置cgroup来限制服务资源争抢 1、场景 我们的宿主机上通常会用systemctl来管理一些agent服务&#xff0c;此时我们需要限制服务的cpu&#xff0c;memory等资源用量&#xff0c;以防止服务之前互相争抢资源&#xff0c;导致某些核心agent运行异常 2、systemd与cgro…

生成树协议 — STP

目录 一、环路的出现 1、广播风暴&#xff1a; 2、MAC地址表翻滚&#xff1a; 二、生成树 1、定义&#xff1a; 2、生成树使用的算法&#xff1a; 三、802.1D 1、BPDU&#xff1a; 2、TCN—拓扑变更消息&#xff08;也是BPDU&#xff09;&#xff1a; 3、部分名词&am…

【Python小游戏】某程序员将套圈游戏玩儿到了巅峰,好嗨哟~Pygame代码版《牛牛套圈》已上线,大人的套圈游戏太嗨了,小孩勿进。

前言 世上选择那么多。 关注栗子同学会是您最明智的选择哦。 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 “幸运牛牛套圈圈”套住欢乐&#xff0c;圈住幸福&#xff0c;等你来挑战&#xf…

用OpeAI API打造ChatGPT桌面端应用

用OpeAI API打造ChatGPT桌面端应用 自从《如何用ChatGPT高效完成工作》这篇文章火了之后&#xff0c;我在公司内部分享了一下”摸鱼“的先进经验&#xff0c;激发起广大同事一起”摸鱼“的热情。但是注册ChatGPT账号非常麻烦&#xff0c;既要Science上网&#xff0c;又要海外手…

Spark环境搭建

文章目录Spark 概述Spark 发展历史使用现状官网介绍流行原因组成模块Spark环境搭建-Local模式(本地模式)Spark环境搭建-Standalone(独立集群)Spark环境搭建-Standalone-HA(高可用)Spark环境搭建-Spark-On-Yarn两种模式Spark 概述 Spark 发展历史 2009年诞生2014年成为Apache顶…

Java笔记-线程中断

线程的中断 1.应用场景&#xff1a; 假设从网络下载一个100M的文件&#xff0c;如果网速很慢&#xff0c;用户等得不耐烦&#xff0c;就可能在下载过程中点“取消”&#xff0c;这时&#xff0c;程序就需要中断下载线程的执行。 2.常用中断线程的方法&#xff1a; 1.使用标…

Canvas鼠标滚轮缩放以及画布拖动(图文并茂版)

Canvas鼠标滚轮缩放以及画布拖动 本文会带大家认识Canvas中常用的坐标变换方法 translate 和 scale&#xff0c;并结合这两个方法&#xff0c;实现鼠标滚轮缩放以及画布拖动功能。 Canvas的坐标变换 Canvas 绘图的缩放以及画布拖动主要通过 CanvasRenderingContext2D 提供的 …

C++设计模式(13)——装饰模式

亦称&#xff1a; 装饰者模式、装饰器模式、Wrapper、Decorator 意图 装饰模式是一种结构型设计模式&#xff0c; 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 问题 假设你正在开发一个提供通知功能的库&#xff0c; 其他程序可使用它向用户发…

注册ChatGPT的辛酸血泪史,不能算教程的教程

注册ChatGPT的血泪史 2月份了&#xff0c;改论文降重了&#xff0c;所以想搞个ChatGPT玩玩&#xff0c;本以为有渠道能顺序上车&#xff0c;但是看了很多教程&#xff0c;也进了很多交流群&#xff0c;都喵的(要不是卖号&#xff0c;租体验&#xff0c;liar)&#xff0c;所以自…

java ssm高校教材管理平台 idea maven

设计并且实现一个基于JSP技术的高校教材管理平台的设计与实现。采用MYSQL为数据库开发平台&#xff0c;SSM框架&#xff0c;Tomcat网络信息服务作为应用服务器。高校教材管理平台的设计与实现的功能已基本实现&#xff0c;主要学生、教材管理、学习教材、教材入库、教材领取、缴…

C语言 大数加法 大数乘法

最近刷题&#xff0c;总遇到大数加法&#xff08;浮点数&#xff09;和乘法问题(阶乘)&#xff0c;总结一下思路。 大数乘法主要思想&#xff1a;编程实现竖式乘法&#xff08;小学时候学的列竖式计算乘法&#xff09;创建一个很大的数组&#xff0c;用于存储大数的每一位。&am…

Android Q WiFi 代码框架

同学,别退出呀,我可是全网最牛逼的 WIFI/BT/GPS/NFC分析博主,我写了上百篇文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦。 1 wifi 架构图 备注:在Android 9.0中,WiFi的状态处理在WifiStateMachine中进行,到…

c语言数据结构-图的遍历

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 定义&#xff1a; 两种遍历方法&#xff1a; 深度优先搜索&#xff08;DFS&#xff09;&#xff1a; …

ElasticJob-Lite架构篇 - 认知分布式任务调度ElasticJob-Lite

前言 本文基于 ElasticJob-Lite 3.x 版本展开分析。 如果 Quartz 集群中有多个服务端节点&#xff0c;任务决定在哪个服务端节点上执行的呢&#xff1f; Quartz 采用随机负载&#xff0c;通过 DB 抢占下一个即将触发的 Trigger 绑定的任务的执行权限。 在 Quartz 的基础上&…

从0到1一步一步玩转openEuler--10 openEuler基础配置-设置kdump

10 openEuler基础配置-设置kdump 文章目录10 openEuler基础配置-设置kdump10.1 设置kdump10.1.1 设置kdump预留内存10.1.1.1 预留内存参数格式10.1.2 预留内存推荐值10.1.3 禁用网络相关驱动10.1 设置kdump 本节介绍如何设置kdump预留内存及修改kdump配置文件参数。 10.1.1 设…

写python爬虫,你永远绕不过去代理问题

如果你想要从事 Python 爬虫相关岗位&#xff0c;那你一定会接触到代理问题&#xff0c;随之而来的就是下面 5 大代理知识点。 什么是代理&#xff1a;代理是网络中间人&#xff08;中间商赚插件&#xff09;&#xff0c;它代表用户发送网络请求&#xff0c;隐藏用户的真实身份…

JDY-31蓝牙模块使用指南

前言 本来是想买个hc-05&#xff0c;这种非常常用的模块&#xff0c;但是在优信电子买的时候&#xff0c;说有个可以替代的&#xff0c;没注意看&#xff0c;买回来折腾半天。 这个模块是从机模块&#xff0c;蓝牙模块分为主机从机和主从一体的&#xff0c;主机与从机的区别就…