Springboot扩展点之BeanFactoryPostProcessor

news2024/11/16 7:31:45

Springboot扩展点之BeanFactoryPostProcessor

1.功能特性

  1. BeanFactoryPostProcessor的执行是Spring Bean生命周期非常重要的一部分;

  1. BeanFactory级别的后置处理器,在Spring生命周期内,org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory只会执行一次;

  1. 允许在容器读取到Bean的BeanDefinition数据之后,bean未实例化前,读取BeanDefiniion数据,并且可以根据需要进行修改;

2.实现方式

  1. 定义一个Dog类,name属性默认为“旺财”,颜色默认为“黑色”;

@Data
@Component
public class Dog {
    private String name="旺财";
    private String color="黑色";
}
  1. 定义一个实现类(MyBeanFactoryPostProcessor),实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口,重写postProcessBeanFactory()方法,并Dog类的属性name修改为“狗蛋”;并用@Component注解标记BeanFactoryPostProcessor接口的实现类(MyBeanFactoryPostProcessor);

@Component
@Slf4j
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("com.fanfu.config.MyBeanFactoryPostProcessor.postProcessBeanFactory被执行");
        ScannedGenericBeanDefinition dog = ((ScannedGenericBeanDefinition) beanFactory.getBeanDefinition("dog"))  ;
        MutablePropertyValues propertyValues = dog.getPropertyValues();
        propertyValues.addPropertyValue("name", "狗蛋儿");
        log.info("修改Dog的BeanDefinition对象中的name属性为狗蛋儿");
    }
}
  1. 编写单元测试验证结果;

@SpringBootTest
@Slf4j
public class FanfuApplicationTests {
    @Test
    public void test(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
        Dog dog = ((Dog) context.getBean("dog"));
        log.info(dog.getName());
        Assert.isTrue(dog.getName().equals("狗蛋儿"), "属性修改失败");
    }
}
  1. 验证结果表明,自定义的BeanFactoryPostProcessor接口的实现类(MyBeanFactoryPostProcessor),可以在容器读取到Bean的BeanDefinition数据之后,bean未实例化前,读取BeanDefiniion数据,并且根据需要进行修改,那么自定义的BeanFactoryPostProcessor接口的实现类(MyBeanFactoryPostProcessor)的工作原理是什么呢?BeanFactoryPostProcessor接口的实现类是什么时候实例化的?MyBeanFactoryPostProcessor#postProcessBeanFactory方法是如何被调用的?接着往下看。

3.工作原理

3.1BeanFactoryPostProcessor接口的实现类是什么时候实例化的?

  1. BeanFactoryPostProcessor接口的实现类(MyBeanFactoryPostProcessor)被@Component标记,在窗口启动的时候会被封装成BeanDefinition对象注册到容器中;

  1. PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法被执行时,会按照类型从Spring容器中找到所有BeanFactoryPostProcessor类型的实现类的名称;

  1. 在上一步中得到所有BeanFactoryPostProcessor类型的实现类的名称的名称后,再循环一次,来对BeanFactoryPostProcessor的实现类进行实例化 (beanFacotry.getBean()去获取MyBeanFactoryPostProcessor的实例,如果获取不到,就创建一个);

3.2MyBeanFactoryPostProcessor#postProcessBeanFactory方法是如何被调用的?

  1. 在单元测试中构建了一个AnnotationConfigApplicationContext对象,AnnotationConfigApplicationContext的构造方法如下:

public AnnotationConfigApplicationContext(String... basePackages) {
   this();
   scan(basePackages);
   refresh();
}
  1. 从上面的AnnotationConfigApplicationContext的构造方法中,可以看到又调用了refresh(),这里实际最终被调用的是org.springframework.context.support.AbstractApplicationContext#refresh(),这个方法也是Spring容器启动的关键方法,在分析Spring相关的源码时会经常碰到。

  1. AbstractApplicationContext#refresh()中,调用org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法才正式开始了BeanFactoryPostProcessor接口的所有实现类的postProcessBeanFactory()方法调用;

  1. 跟着AbstractApplicationContext#invokeBeanFactoryPostProcessors方法进去,会发现这里只是一个入口,实际承担调用执行任务的是org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法;

  1. 跟着PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法进去后,会发现里面真的是别有洞天,很容易迷路(牢牢带着自己的问题分析源码找答案,不要被除自己问题以外的东西迷了眼,一定会柳暗花明),另外org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor的实现类的调用也在这个方法,所以这个方法的含金量很高,那就单独拎出来仔细分析一下,建议debug一步一步看,多看几遍就能明白,其实也很简单,唯一的难点就是这个方法有点长,需要多点耐心和时间。

public static void invokeBeanFactoryPostProcessors(
      ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
   //之所以说这个方法的含金量很高,
   //是因为在这个方法里是先执行BeanDefinitionRegistryPostProcessor实现类的postProcessBeanDefinitionRegistry方法; 
   //然后才接着执行BeanFactoryPostProcessor接口的实现类的postProcessBeanFactory方法
   //这两个接口很表面上看很类似,实际在执行的时机和功能上是有明显区别的
   Set<String> processedBeans = new HashSet<>();
   //AnnotationConfigApplicationContext继承于GenericApplicationContext,
   //而GenericApplicationContext又实现了BeanDefinitionRegistry接口
   //所以这里会进入if语句中
   if (beanFactory instanceof BeanDefinitionRegistry) {
      BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
      List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
      List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
        //这里提前执行的BeanFactoryPostProcessor,是在准备上下文环境时,发布了ApplicationPreparedEvent事件;
        //触发监听器,通过AbstractApplicationContext#addBeanFactoryPostProcessor注册进来的;
        //这里并不是这次要重点分析的内容,可以先跳过这; 
      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
         if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
            BeanDefinitionRegistryPostProcessor registryProcessor =
                  (BeanDefinitionRegistryPostProcessor) postProcessor;
            registryProcessor.postProcessBeanDefinitionRegistry(registry);
            registryProcessors.add(registryProcessor);
         }
         else {
            regularPostProcessors.add(postProcessor);
         }
      }
      List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
      // 从32行到72行,是在执行BeanDefinitionRegistryPostProcessor实现类的postProcessBeanDefinitionRegistry方法;
      //执行的过程也是有点小区折的,分三步,第一,执行实现了PriorityOrdered接口的实现类.
      String[] postProcessorNames =
            beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
         if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
         }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      currentRegistryProcessors.clear();
      // 第二,执行实现了Ordered接口的实现类;
      postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
         if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
         }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      currentRegistryProcessors.clear();
      //第三,执行剩下其他的BeanDefinitionRegistryPostProcessor实现类;
      boolean reiterate = true;
      while (reiterate) {
         reiterate = false;
         postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
         for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName)) {
               currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
               processedBeans.add(ppName);
               reiterate = true;
            }
         }
         sortPostProcessors(currentRegistryProcessors, beanFactory);
         registryProcessors.addAll(currentRegistryProcessors);
         invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
         currentRegistryProcessors.clear();
      }
      // BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,
      //所以这部分实现类的postProcessBeanFactory()会提前执行
      invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
      //第26行,非BeanDefinitionRegistryPostProcessor类型的BeanFactoryPostProcessor实现类会在这执行
      invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
   } else {
      invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
   }
   //BeanDefinitionRegistryPostProcessor接口的实现类上面已执行执行完了
   //下面开始准备执行BeanFactoryPostProcessor接口的实现类
   String[] postProcessorNames =
         beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
   // 正式执行前,把BeanFactoryPostProcessor接口的实现类分成了三类,
   //分别是实现了PriorityOrdered接口,实现了Ordered接口,其他;
   List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
   List<String> orderedPostProcessorNames = new ArrayList<>();
   List<String> nonOrderedPostProcessorNames = new ArrayList<>();
   for (String ppName : postProcessorNames) {
      if (processedBeans.contains(ppName)) {
         // skip - already processed in first phase above
      }
      else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
         priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
      }
      else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
         orderedPostProcessorNames.add(ppName);
      }
      else {
         nonOrderedPostProcessorNames.add(ppName);
      }
   }
   // 分好类,第一,先执行实现了PriorityOrdered接口的实现类; 
   sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
   invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
   // 第二,执行实现了Ordered接口的实现类; 
   List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
   for (String postProcessorName : orderedPostProcessorNames) {
      orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
   }
   sortPostProcessors(orderedPostProcessors, beanFactory);
   invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
   //第三,执行未实现上面两个接口的实现类,自定义的MyBeanFactoryPostProcessor就是在这里被执行的
   //其实,也很简单的,和BeanDefinitionRegistryPostProcessor接口的实现类的执行过程类似; 
   List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
   for (String postProcessorName : nonOrderedPostProcessorNames) {
      nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
   }
   invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
   beanFactory.clearMetadataCache();
}

3.3调用时序图

这里我画了一个时序图,可以更直观的看到整个调用过程,也可以照着这个图,一步一步debug来了解整个过程;

3.4postProcessBeanFactory()postProcessBeanDefinitionRegistry()

通过分析PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法,postProcessBeanFactory()与postProcessBeanDefinitionRegistry()的区别已经很明显了,这里再总结一下(总结的有不准的地方,还请小伙伴在评论区告诉我,一块进步):

  1. BeanDefinitionRegistryPostProcessor接口的实现类的postProcessBeanDefinitionRegistry方法要优先于BeanFactoryPostProcessor接口的实现类的postProcessBeanFactory方法执行;

  1. postProcessBeanDefinitionRegistry方法形参是BeanDefinitionRegistry,postProcessBeanFactory方法的形参是ConfigurableListableBeanFactory,在功能上会有一些区别;需要注意的是DefaultListableBeanFactory实现了BeanDefinitionRegistry和ConfigurableListableBeanFactory接口;

  1. BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,关于BeanDefinitionRegistryPostProcessor可以移步这里:Springboot扩展点之BeanDefinitionRegistryPostProcessor

4.应用场景

对敏感信息的解密处理,比如数据库连接信息加密和解密:在实际的业务开发中,在配置文件中明文配置mysq,redis的密码实际上是不安全的,需要配置加密后的密码信息; 但是把加密后的密码信息注入的数据源中,去连接mysql数据库肯定会连接异常,因为mysql并不知道你的加密方式和加密方法。这就会产生一个需求:需要在配置文件中配置的数据库信息是加密的,但是在把密码信息注入数据源前在程序里解密处理。

BeanFactoryPostProcessor正好可以解决这个问题,在真正使用到数据源去连接数据库前,读取到加密信息,进行解密处理,再用解密后的信息替换掉Spring容器中加密信息。

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

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

相关文章

【C语言】10题相关讲解+总结----有用的知识1

总结【C语言】10题&#xff0c;有兴趣的可以看看1.结构体与typedef联系2.结构体中涉及的操作符3.指针数组与数组指针4.数组首元素的作用5.喝汽水问题6.上三角矩阵判定7 矩阵相等判定8.VS调试技巧9.Debug与Release关系10.调整奇数偶数顺序11.有序序列合并1.结构体与typedef联系 …

开发互动直播应用很简单:声网 Android Demo保姆级运行教程

本文作者是来自声网开发者社区的用户“Xiaohua”。 前言 本人在参与《声网开发者漫游指南》期间&#xff0c;通过学习了解和学会跑通声网的实时互动Demo&#xff0c;但因为课程提供的demo是移动端和pc端的&#xff0c;很少接触过&#xff0c;所以只能花点时间学习一下才能运行…

如何屏蔽 iOS 软件自动更新,去除更新通知和标记

如何禁用 iPhone、iPad 软件自动更新。适用于 iOS、iPadOS 和 watchOS&#xff0c;即 iPhone、iPad 和 Apple Watch 通用。 请访问原文链接&#xff1a;https://sysin.org/blog/disable-ios-update/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&a…

WebAssembly编译之(4)-WASM编译进阶(多文件、多接口)

引言 上一节我们讲到如何用Emscripten将一个C编译陈wasm&#xff0c;并导出可供Javascirpt调用的接口&#xff0c;以及C导出类的函数接口、导出类的封装对象等。然而&#xff0c;编译的方式比较玛法&#xff0c;有没办法能更友好一点实现wasm的编译呢 WASM 相关文档&#xff1a…

【自学Docker】Docker diff命令

Docker diff命令 大纲 docker diff命令教程 docker diff 命令用于比较一个 Docker容器 不同版本提交的文件差异。该命令后面的 CONTAINER 可以是容器Id&#xff0c;或者是容器名。 docker diff命令会列出 3 种容器内文件状态变化&#xff08;A - Add, D - Delete, C - Chang…

Java-基础-3.容器

一&#xff1a;为什么会出现容器&#xff1f; 在之前的学习中&#xff0c;我们学习了变量和常量。都是一个字符或者字符串&#xff0c;数字的情况。但是在实际的生产中&#xff0c;我们一次会接受到很多类型不同&#xff0c;个数不同的数据。所以&#xff0c;为了方便我们后续…

红杉:2022企业数字化年度指南

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年12月份热门报告盘点罗振宇2023年跨年演讲PPT原稿吴晓波2022年年终秀演讲PPT原稿2023年&#xff0c;如何科学制定年度规划&#xff1f;《底层逻辑》高清配图华为2021数字…

[基础语法] python语法之列表的基本操作

文章目录列表已发布列表的基本操作增删改查排序列表实例练习列表 已发布 python判断语句python循环语句python之列表list python 的数据格式主要有列表、字典、元组、集合。其中列表的使用最为广泛。 任何一种数据格式的使用都离不开增、删、改、查四个操作。列表除了这四个…

【Mysql第四期 运算符规则计算】

文章目录写在前面1.算数运算符2.比较运算符3.逻辑运算符4.位运算符5.运算符的优先级拓展&#xff1a;使用正则表达式查询写在前面 基本的运算符号在计算机编程领域都是相通的&#xff0c;会有自己的一些特定符号语言&#xff0c;就像是各地的普通话一样&#xff0c;尽管语音描…

剑指 Offer II 004只出现一次的数字

给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 示例 1&#xff1a; 输入&#xff1a;nums [2,2,3,2] 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;nums [0,1,0,…

Linux中Vi编辑器和Vim编辑器

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

Docker常用命令总结

基础命令 1.启动docker systemctl start docker 2.关闭docker systemctl stop docker 3.设置docker为自启动 systemctl enable --now docker 4.重启docker systemctl restart docker 3.查看docker版本信息 docker version 4.查看docker详细信息 docker info Clien…

Spring定时器超过30分钟问题

目前需要定时器做一个定时扫描任务的功能&#xff0c;原先都是定时在半个小时&#xff0c;程序跑起来也没事。但是最近公司要求定时时间加长到45分钟&#xff0c;而调整完配置完后发现&#xff0c;程序是在45分钟和整点进行的扫描。 下面是我做的示例时间缩短为45秒 spring。x…

守护进程编程流程及代码实现

概念不做阐述&#xff0c;本文主要内容为守护进程编程部分的知识说明 守护进程的编程流程&#xff1a; 1.fork退出父进程&#xff0c;保证留下的子进程是组员进程 2.利用setsid()创建新会话&#xff0c;把子进程挪到新的会话中 //获取会话是getsid() 3.fork退出父进程&#x…

完成基于Servlet的对user表的增删改查

基于Servlet的增删改查 1.开发环境 IDEAJDK1.8Tomcat8.5Mysql 8.0.12 2.数据库 2.1表创建 2.2表数据 3.JavaWeb代码 3.1目录结构 3.2util包下代码 JdbcUtil完成对数据库的连接和资源释放 JsonResult对返回前端资源的封装 JdbcUtil代码&#xff1a; /* 数据库连接板帮助类 …

Python中的垃圾回收机制

Python的垃圾回收主要以引用计数为主&#xff0c;分代回收为辅。引用计数在Python中&#xff0c;使用了引用计数这一技术实现内存管理。一个对象被创建完成后就有一个变量指向这个对象&#xff0c;那么就这个对象的引用计数为1&#xff0c;以后如果有其他变量指向这个对象&…

不吹牛,完爆ant design的定位组件,floating-ui来也

前言 因为要写react定位组件&#xff08;这不是标题党&#xff0c;就是完爆ant design的定位组件&#xff0c;你应该看到一半就会同意我的观点&#xff09;&#xff0c;如下图&#xff1a; 红框部分是用绝对定位放在按钮上面的&#xff0c;你们B端用的主流组件库都是这样实现的…

Python自动化小技巧14——自动批量发送邮件(带各种附件)

案例背景 我的博客下面评论都是各种要数据的......一个一个发其实很浪费时间的&#xff0c;每次输入评论者的邮箱&#xff0c;然后打开数据所在的文件夹&#xff0c;上传&#xff0c;填写标题正文&#xff0c;发送....... 一模一样的流程&#xff0c;所以这种重复性的劳动肯定…

Linux下 git 上传与删除 的基本指令

git的概述克隆仓库使用 git 上传文件删除 git 中的文件git的概述 Git 是一个免费并开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。 在使用 git 应确保Linux系统中已安装有git 命令&#xff1a;git --version 作用&#xff1a;查看 git 是否…

Spring Boot 单元测试

文章目录1. 单元测试是什么2. 单元测试的优点3. 进行 Spring Boot 单元测试3.1 确认项目中已经内置了测试框架3.2 生成单元测试的类3.3 添加 SpringBootTest 注解3.4 添加单元测试的业务代码3.5 注解 Transactional4. 断言1. 单元测试是什么 单元测试&#xff0c;是指对软件中…