当前位置:首页 > 科技  > 软件

SpringBoot扩展点之BeanPostProcessor

来源: 责编: 时间:2023-12-01 17:15:12 304观看
导读前言Springboot(Spring)的扩展点其实有很多,但是都有一个共同点,都是围绕着Bean和BeanFactory(容器)展开的,其实这也很好理解,Spring的核心是控制反转、依赖注入、面向切面编程,再抛开所有的枝枝节节,你发现了什么?Spring提供了

前言

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

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

1、BeanPostProcessor接口的功能特性是什么样的?id828资讯网——每日最新资讯28at.com

2、BeanPostProcessor接口怎么实现扩展?id828资讯网——每日最新资讯28at.com

3、BeanPostProcessor接口的实现类的工作原理是什么?id828资讯网——每日最新资讯28at.com

4、BeanPostProcessor接口的应用场景有哪些?id828资讯网——每日最新资讯28at.com

功能特性

1、BeanPostProcessor是Bean级别的扩展接口,在Spring管理的Bean实例化完成后,预留了两种扩展点;id828资讯网——每日最新资讯28at.com

2、这两处扩展的实现方式就是实现BeanPostProcessor接口,并将实现类注册到Spring容器中;id828资讯网——每日最新资讯28at.com

3、两种扩展点分别是BeanPostProcessor接口的postProcessBeforeInitialization方法和postProcessAfterInitialization方法;id828资讯网——每日最新资讯28at.com

4、postProcessBeforeInitialization方法的执行时机是在Spring管理的Bean实例化、属性注入完成后,InitializingBean#afterPropertiesSet方法以及自定义的初始化方法之前;id828资讯网——每日最新资讯28at.com

5、postProcessAfterInitialization方法的执行时机是在InitializingBean#afterPropertiesSet方法以及自定义的初始化方法之后;id828资讯网——每日最新资讯28at.com

6、BeanPostProcessor接口的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法,在在Spring管理的每个bean初始化后都会执行到;id828资讯网——每日最新资讯28at.com

实现方式

1、定义一个实体类Dog,并实现InitializingBean接口,并且实现afterPropertiesSet()。其中afterPropertiesSet()和init()是为了演示BeanPostProcessor接口的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法的执行时机;id828资讯网——每日最新资讯28at.com

@Getter@Setter@Slf4jpublic 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实例化后的初始化方法;id828资讯网——每日最新资讯28at.com

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

2、定义MyBeanPostProcessor,并且实现BeanPostProcessor接口;(这里类的命名和方法内逻辑仅是为了演示需要,实际开发中需要以实际逻辑来替换掉演示内容)id828资讯网——每日最新资讯28at.com

@Component@Slf4jpublic 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、编写单元测试,来验证结果;id828资讯网——每日最新资讯28at.com

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

图片图片id828资讯网——每日最新资讯28at.com

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

以上演示了BeanPostProcessor作为Springboot的扩展点之一的实现方式和执行时机,下面从示例入手,来了解一下其基本的工作原理,正所谓知其然还要知其所以然嘛。id828资讯网——每日最新资讯28at.com

工作原理

BeanPostProcessor的工作原理的关键其实就是两点,第一,BeanPostProcessor的实现类是什么时候被注册的?第二,BeanPostProcessor的实现类的postProcessBeforeInitialization方法和postProcessAfterInitialization方法是如何被执行的?id828资讯网——每日最新资讯28at.com

注册时机

1、BeanPostProcessor中的两个扩展方法中,postProcessBeforeInitialization方法是先被执行的,即Bean实例化和属性注入完成之后,通过实现方式示例代码的Debug,找到了BeanPostProcessor接口的实现类到Spring容器中的入口,即AbstractApplicationContext#refresh--->registerBeanPostProcessorsid828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

2、进入到AbstractApplicationContext#registerBeanPostProcessors方法内,会发现这段代码很干净,即依赖于PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法;id828资讯网——每日最新资讯28at.com

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

3、进入到PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法又是另一番洞天:第一步,获取所有实现BeanPostProcessor接口的实现类的名称,实现方式示例中的MyBeanPostProcessors就在其中;id828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

第二步,提前注册BeanPostProcessorChecker,主要用途是用于Bean创建过程中的日志信息打印记录;id828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

第三步,就是把所有的BeanPostProcessor接口的实现类,按照是否实现PriorityOrdered接口、是否实现Ordered接口、其他,分为三组;id828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

最后,下面的内容很长,不过很简单,即按第二步分成的三类,依次注册,具体的顺序是实现PriorityOrdered接口BeanPostProcessor接口的实现类、实现实现Ordered接口BeanPostProcessor接口的实现类、其他的BeanPostProcessor接口的实现类;id828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

总结,BeanPostProcessor的注册时机是在Spring容器启动过程中,即BeanFactoryPostProcessor扩展点的逻辑执行完成后,紧接着就开始了BeanPostProcessor的注册,其具体的注册逻辑在PostProcessorRegistrationDelegate#registerBeanPostProcessors()。id828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

执行时机

从实现方式的示例中验证得知,BeanPostProcessor接口的实现类的执行时机是在Spring管理的Bean实例化、属性注入完成后,那么找到Dog类的实例化入口,那么离BeanPostProcessor接口的实现类的执行时机也就不远了。id828资讯网——每日最新资讯28at.com

1、通过Debug调试,注册到Spring容器中的Dog类的实例化入口,即org.springframework.context.support.AbstractApplicationContext#refresh--->finishBeanFactoryInitialization();id828资讯网——每日最新资讯28at.com

图片图片id828资讯网——每日最新资讯28at.com

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

图片图片id828资讯网——每日最新资讯28at.com

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

图片图片id828资讯网——每日最新资讯28at.com

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

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接口的实现类,然后再遍历执行触发方法,就这么朴实无华。id828资讯网——每日最新资讯28at.com

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方法,如果有,则在这里开始执行;id828资讯网——每日最新资讯28at.com

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()看懂了,这里就没有必要分析了,如出一辙,熟悉配方,熟悉的味道。id828资讯网——每日最新资讯28at.com

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的工作原理分析完了,归根结底就是两点,id828资讯网——每日最新资讯28at.com

第一,在Spring容器初始化的过程中,完成扩展点的注册;id828资讯网——每日最新资讯28at.com

第二,在Spring中Bean完成实例化和属性注入后,开始触发已注册的扩展点的扩展动作。id828资讯网——每日最新资讯28at.com

内容很长,但是逻辑简单,希望阅读到这篇文章的小伙伴能够有耐心看完,因为我在研究清楚整个过程后,我是感觉获益良多的,希望你也是。id828资讯网——每日最新资讯28at.com

应用场景

其实了解了BeanPostProcessor的功能特性、实现方式和工作原理,在遇到类似的业务需求的时候都可以应用这个扩展点,这里举两个我想到的应用场景:id828资讯网——每日最新资讯28at.com

处理自定义注解

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

参数校验

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

本文链接:http://www.28at.com/showinfo-26-35885-0.htmlSpringBoot扩展点之BeanPostProcessor

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 微服务,其实它也是有很多坑

下一篇: 12种常见的软件架构风格,架构师必备

标签:
  • 热门焦点
  • 直屏旗舰来了 iQOO 12和K70 Pro同台竞技

    旗舰机基本上使用的都是双曲面屏幕,这就让很多喜欢直屏的爱好者在苦等一款直屏旗舰,这次,你们等到了。据博主数码闲聊站带来的最新爆料称,Redmi下代旗舰K70 Pro和iQOO 12两款手
  • 7月安卓手机性能榜:红魔8S Pro再夺榜首

    7月份的手机市场风平浪静,除了红魔和努比亚带来了两款搭载骁龙8Gen2领先版处理器的新机之外,别的也想不到有什么新品了,这也正常,通常6月7月都是手机厂商修整的时间,进入8月份之
  • 掘力计划第 20 期:Flutter 混合开发的混乱之治

    在掘力计划系列活动第20场,《Flutter 开发实战详解》作者,掘金优秀作者,Github GSY 系列目负责人恋猫的小郭分享了Flutter 混合开发的混乱之治。Flutter 基于自研的 Skia 引擎
  • 三言两语说透柯里化和反柯里化

    JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是两种很有用的技术,可以帮助我们写出更加优雅、泛用的函数。本文将首先介绍柯里化和反柯里化的概念、实现原理和应用
  • 微信语音大揭秘:为什么禁止转发?

    大家好,我是你们的小米。今天,我要和大家聊一个有趣的话题:为什么微信语音不可以转发?这是一个我们经常在日常使用中遇到的问题,也是一个让很多人好奇的问题。让我们一起来揭开这
  • .NET 程序的 GDI 句柄泄露的再反思

    一、背景1. 讲故事上个月我写过一篇 如何洞察 C# 程序的 GDI 句柄泄露 文章,当时用的是 GDIView + WinDbg 把问题搞定,前者用来定位泄露资源,后者用来定位泄露代码,后面有朋友反
  • 小米MIX Fold 3下月亮相:今年唯一无短板的全能折叠屏

    这段时间以来,包括三星、一加、荣耀等等有不少品牌旗下的最新折叠屏旗舰都有新的进展,其中荣耀、三星都已陆续发布了最新的折叠屏旗舰,尤其号荣耀Magi
  • 华为HarmonyOS 4.0将于8月4日发布 或搭载AI大模型技术

    华为宣布HarmonyOS4.0将于8月4日正式发布。此前,华为已经针对开发者公布了HarmonyOS4.0,以便于开发者提前进行适配,也因此被曝光出了一些新系统的特性
  • 三星Galaxy Z Fold5官方渲染图曝光:13.4mm折叠厚度依旧感人

    据官方此前宣布,三星将于7月26日在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy Z Flip 5、
Top