spring4.1.8扩展实战之五:改变bean的定义(beanfactorypostprocessor接口)(代码片段)

程序员欣宸 程序员欣宸     2022-11-28     229

关键词:

欢迎访问我的GitHub

本篇概览

  • 本章我们继续实战spring的扩展能力,通过自定义BeanFactoryPostProcessor接口的实现类,来对bean实例做一些控制;

BeanFactoryPostProcessor接口简介

  • spring容器初始化时,从资源中读取到bean的相关定义后,保存在beanFactory的成员变量中(参考DefaultListableBeanFactory类的成员变量beanDefinitionMap),在实例化bean的操作就是依据这些bean的定义来做的,而在实例化之前,spring允许我们通过自定义扩展来改变bean的定义,定义一旦变了,后面的实例也就变了,而beanFactory后置处理器,即==BeanFactoryPostProcessor==就是用来改变bean定义的;

源码分析

  • 一起来看看上述功能对应的源码,从AbstractApplicationContext类的refresh方法看起,这里面对应着容器初始化的基本操作;

  • 如下图所示,红框中的invokeBeanFactoryPostProcessors方法用来找出所有beanFactory后置处理器,并且调用这些处理器来改变bean的定义:

  • 打开invokeBeanFactoryPostProcessors方法,如下所示,实际操作是委托PostProcessorRegistrationDelegate去完成的:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) 
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    
  • 在调用PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法时,注意第二个入参是getBeanFactoryPostProcessors()方法,该方法返回的是applicationContext的成员变量beanFactoryPostProcessors,该成员变量的值是哪里设置的呢?查找后发现,来自AbstractApplicationContext.addBeanFactoryPostProcessor方法被调用的时候:
@Override
public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) 
    Assert.notNull(postProcessor, "BeanFactoryPostProcessor must not be null");
    this.beanFactoryPostProcessors.add(postProcessor);
  • AbstractApplicationContext.addBeanFactoryPostProcessor方法是留给业务扩展时调用的,例如在springboot初始化时,ConfigurationWarningsApplicationContextInitializer类的initialize方法中就有调用:
@Override
public void initialize(ConfigurableApplicationContext context) 
    context.addBeanFactoryPostProcessor(
            new ConfigurationWarningsPostProcessor(getChecks()));
  • 看过了如何添加BeanFactoryPostProcessor,再回到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,看看如何处理这些BeanFactoryPostProcessor,整个invokeBeanFactoryPostProcessors太大,不在此粘贴所有代码了,主要是分成七段来看分析这个方法:
  • 第一段,入参中的BeanFactoryPostProcessor,按照是否实现了BeanDefinitionRegistryPostProcessor,分别放入两个集合:registryProcessors和regularPostProcessors;
  • 第二段,找出所有实现了BeanDefinitionRegistryPostProcessor接口和PriorityOrdered接口的bean,放入registryProcessors集合,放入根据PriorityOrdered接口来排序,然后这些bean会被invokeBeanDefinitionRegistryPostProcessors方法执行;
  • 第三段,找出所有实现了BeanDefinitionRegistryPostProcessor接口和Ordered接口的bean,放入registryProcessors集合,放入根据PriorityOrdered接口来排序,然后这些bean会被invokeBeanDefinitionRegistryPostProcessors方法执行;
  • 第四段,对于那些实现了BeanDefinitionRegistryPostProcessor接口,但是没有实现PriorityOrdered和Ordered的bean也被找出来,然后这些bean会被invokeBeanDefinitionRegistryPostProcessors方法执行;
  • 第五段,入参中的BeanFactoryPostProcessor,没有实现BeanDefinitionRegistryPostProcessor的那些bean,被invokeBeanDefinitionRegistryPostProcessors;
  • 第六段,接下来的代码需要重点关注:==找出实现了BeanFactoryPostProcessor接口的bean,注意这里已将面实现了BeanDefinitionRegistryPostProcessor接口的bean给剔除了,将这些bean分为三类:实现了PriorityOrdered接口的放入priorityOrderedPostProcessors,实现了Ordered接口的放入orderedPostProcessorNames,其他的放入nonOrderedPostProcessorNames,这段代码是关键,因为我们自定义的实现BeanFactoryPostProcessor接口的bean就会在此处被找出来==,如下:
String[] postProcessorNames =
                beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

        // Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
        // Ordered, and the rest.
        List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
        List<String> orderedPostProcessorNames = new ArrayList<String>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
        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);
            
        
  • 第七段,priorityOrderedPostProcessors和orderedPostProcessorNames这两个集合,都是先做排序再调用invokeBeanDefinitionRegistryPostProcessors方法,最后是nonOrderedPostProcessorNames集合,也被传入invokeBeanDefinitionRegistryPostProcessors方法;

  • 从上面的分析可以发现,所有实现了BeanFactoryPostProcessor接口的bean,都被作为入参,然后调用了invokeBeanDefinitionRegistryPostProcessors或者invokeBeanFactoryPostProcessors方法去处理,来看看这两个方法:
private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) 

        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) 
            postProcessor.postProcessBeanDefinitionRegistry(registry);
        
    

    /**
     * Invoke the given BeanFactoryPostProcessor beans.
     */
    private static void invokeBeanFactoryPostProcessors(
            Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) 

        for (BeanFactoryPostProcessor postProcessor : postProcessors) 
            postProcessor.postProcessBeanFactory(beanFactory);
        
    
  • 如上所示,两个方法都很简单,对每个BeanFactoryPostProcessor接口的实现类,都调用了其接口方法,不同的是,对于实现了BeanDefinitionRegistryPostProcessor接口的bean,调用其postProcessBeanDefinitionRegistry方法的时候,入参是BeanDefinitionRegistry,而非BeanFactory,因此,实现了BeanDefinitionRegistryPostProcessor接口的bean,其postProcessBeanDefinitionRegistry在被调用时,可以通过入参BeanDefinitionRegistry来做更多和bean的定义有关的操作,例如注册bean;

  • 至此,对BeanFactoryPostProcessor的处理流程就全部分析完了,这里小结一下:
    1. ApplicationContext扩展类可以调用AbstractApplicationContext.addBeanFactoryPostProcessor方法,将自定义的BeanFactoryPostProcessor实现类保存到ApplicationContext中;
    2. spring容器初始化时,上一步中被加入到ApplicationContext的bean会被优先调用其postProcessBeanFactory方法;
    3. 自定义的BeanFactoryPostProcessor接口实现类,也会被找出来,然后调用其postProcessBeanFactory方法;
    4. postProcessBeanFactory方法被调用时,beanFactory会被作为参数传入,自定义类中可以使用该参数来处理bean的定义,达到业务需求;
    5. 此时的spring容器还没有开始实例化bean,因此自定义的BeanFactoryPostProcessor实现类不要做与bean实例有关的操作,而是做一些与bean定义有关的操作,例如修改某些字段的值,这样后面实例化的bean的就会有相应的改变;

实战BeanFactoryPostProcessor接口的实现类

  • 本次实战的内容是创建一个springboot工程,在里面自定义一个BeanFactoryPostProcessor接口的实现类,如果您不想敲代码,也可以去github下载源码,地址和链接信息如下表所示:
名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本章源码在文件夹customizebeanfactorypostprocessor下,如下图红框所示:

  • 接下来开始实战吧:
  • 基于maven创建一个springboot的web工程,名为customizebeanfactorypostprocessor,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bolingcavalry</groupId>
    <artifactId>customizebeanfactorypostprocessor</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>customizebeanfactorypostprocessor</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.15.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • 定义一个服务接口CalculateService:
package com.bolingcavalry.customizebeanfactorypostprocessor.service;

public interface CalculateService 
    /**
     * 整数加法
     * @param a
     * @param b
     * @return
     */
    int add(int a, int b);

    /**
     * 返回当前实现类的描述信息
     * @return
     */
    String getServiceDesc();
  • 创建CalculateService接口的实现类CalculateServiceImpl,==注意要在Service注解上明确写入bean的名称==:
package com.bolingcavalry.customizebeanfactorypostprocessor.service.impl;

import com.bolingcavalry.customizebeanfactorypostprocessor.service.CalculateService;
import org.springframework.stereotype.Service;

@Service("calculateService")
public class CalculateServiceImpl implements CalculateService 

    private String desc = "desc from class";

    public void setDesc(String desc) 
        this.desc = desc;
    

    @Override
    public int add(int a, int b) 
        return a + b;
    

    @Override
    public String getServiceDesc() 
        return desc;
    
  • 创建一个BeanFactoryPostProcessor接口的实现类CustomizeBeanFactoryPostProcessor,并且用注解Component将其定义为spring环境中的bean:
package com.bolingcavalry.customizebeanfactorypostprocessor.beanfactorypostprocessor;

import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.stereotype.Component;

@Component
public class CustomizeBeanFactoryPostProcessor implements BeanFactoryPostProcessor 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
        AbstractBeanDefinition abstractBeanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition("calculateService");

        MutablePropertyValues pv =  abstractBeanDefinition.getPropertyValues();
        pv.addPropertyValue("desc", "Desc is changed from bean factory post processor");
        abstractBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
    
  • 上述代码的功能很简单,找到名为"calculateService"的bean的定义对象,通过调用addPropertyValue方法,将定义中的desc属性值改为"Desc is changed from bean factory post processor",这样等名为"calculateService"的bean被实例化出来后,其成员变量desc的值就是"Desc is changed from bean factory post processor";

  • 创建启动类CustomizebeanfactorypostprocessorApplication:
package com.bolingcavalry.customizebeanfactorypostprocessor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CustomizebeanfactorypostprocessorApplication 

    public static void main(String[] args) 
        SpringApplication.run(CustomizebeanfactorypostprocessorApplication.class, args);
    
  • 启动应用,浏览器输入地址:http://localhost:8080/add/1/2 ,看到的响应如下图,红框中就是CustomizeBeanFactoryPostProcessor对名为calculateService的bean的定义对象修改后导致的结果:

  • 至此,BeanFactoryPostProcessor的源码分析和扩展实战就结束了,通过本次实战,除了对spring扩展的认识加深,又掌握了一种控制bean的方式;

欢迎关注51CTO博客:程序员欣宸

spring4.1.8扩展实战之八:import注解(代码片段)

欢迎访问我的GitHub在spring框架下做开发时,@Import是常见的注解,可以用来动态创建bean,今天我们先从源码分析原理,再用实战来验证Import的作用;文章概览本章由以下几部分组成:从Enable前缀的注解谈起,揭示常见的Enable注解... 查看详情

spring4.1.8扩展实战之四:感知spring容器变化(smartlifecycle接口)(代码片段)

欢迎访问我的GitHub本篇概览本章是《spring4.1.8扩展实战》的第四篇,如果业务上需要在spring容器启动和关闭的时候做一些操作,可以自定义SmartLifecycle接口的实现类来扩展,本章我们通过先分析再实战的方法,来掌握这种扩展方... 查看详情

spring4.1.8扩展实战之一:自定义环境变量验证

欢迎访问我的GitHub关于扩展在之前学习spring环境初始化源码的过程中,见到有些地方能通过子类来实现自定义扩展,从本章开始,我们来逐个实践这些扩展,除了加深对spring的理解,有的扩展也能解决一些通用的问题;文中涉及... 查看详情

spring4.1.8扩展实战之三:广播与监听

欢迎访问我的GitHub提到广播与监听,我们常常会想到RabbitMQ、Kafka等消息中间件,这些常用于分布式系统中多个应用之间,有时候应用自身内部也有广播和监听的需求(例如某个核心数据发生变化后,有些业务模块希望立即被感... 查看详情

设计模式实战应用之五:工厂方法模式

    工厂方法模式的定义    工厂方法模式的应用相当广泛。工厂方法模式在JavaAPI中的应用比比皆是:java.util.Collection接口的iterator方法就是一个非常著名的工厂方法模式的演示样例;java.net.URLStreamHand... 查看详情

springcloudgateway实战之五:内置filter(代码片段)

...https://github.com/zq2599/blog_demos本篇概览作为《SpringCloudGateway实战》系列的第五篇,是时候了解过滤器(filter)的作用了,本篇咱们一起来了解SpringCloudGateway内置好的过滤器,真是种类繁多功能强大 查看详情

springcloudgateway实战之五:内置filter(代码片段)

...https://github.com/zq2599/blog_demos本篇概览作为《SpringCloudGateway实战》系列的第五篇,是时候了解过滤器(filter)的作用了,本篇咱们一起来了解SpringCloudGateway内置好的过滤器,真是种类繁多功能强大 查看详情

(转载)dll动态链接库编程入门之五:mfc扩展dll

 MFC扩展DLL的内涵为MFC的扩展,用户使用MFC扩展DLL就像使用MFC本身的DLL一样。除了可以在MFC扩展DLL的内部使用MFC以外,MFC扩展DLL与应用程序的接口部分也可以是MFC。我们一般使用MFC扩展DLL来包含一些MFC的增强功能,譬如扩展MFC... 查看详情

bert-多标签文本分类实战之五——bert模型库的挑选与transformers(代码片段)

·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览·下载本实战项目资源:>=点击此处=<[1]BERT模型库  从BERT模型一经Google出世,到tensorflow与pytorch版本的BERT相继发布,再到不同... 查看详情

bert-多标签文本分类实战之五——bert模型库的挑选与transformers(代码片段)

·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览·下载本实战项目资源:>=点击此处=<[1]BERT模型库  从BERT模型一经Google出世,到tensorflow与pytorch版本的BERT相继发布,再到不同... 查看详情

bert-多标签文本分类实战之五——bert模型库的挑选与transformers(代码片段)

·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览·下载本实战项目资源:>=点击此处=<[1]BERT模型库  从BERT模型一经Google出世,到tensorflow与pytorch版本的BERT相继发布,再到不同... 查看详情

swift之五个让swift代码更加优雅的扩展(代码片段)

一、自定义下标来安全访问数组在日常开发中,很容易经历index-out-of-bounds的报错,就是数组越界。如下所示:letvalues=["A","B","C"]values[0]//Avalues[1]//Bvalues[2] 查看详情

vmware后台下citrixxendesktop7.6实战篇之五vcenter凭证安装

1、        2008r2安装调整1.1系統要求1.11DeliveryController支持的操作系统:       WindowsServer2012R2StandardEdition和DatacenterEdition &nb 查看详情

java扩展nginx之五:五大handler(系列最核心)(代码片段)

...):https://github.com/zq2599/blog_demos本篇概览本文是《Java扩展Nginx》系列的第五篇,如题,本篇是整个系列的最核心内容,咱们写的代码主要都集中在nginx-clojure定义的五种handler中,不同handler分别 查看详情

spring4.1.8初始化源码学习三部曲之三:abstractapplicationcontext.refresh方法

欢迎访问我的GitHub我们先回顾ClassPathXmlApplicationContext类的初始化过程如下代码:publicClassPathXmlApplicationContext(String[]configLocations,booleanrefresh,ApplicationContextparent)throwsBeansExceptionsuper(parent);setCon 查看详情

java与es8实战之五:springboot应用中操作es8(带安全检查:https账号密码apikey)(代码片段)

...ff1a;https://github.com/zq2599/blog_demos本篇概览本篇是《java与es8实战》系列的第五篇,总体目标明确:实战在SpringBoot应用中操作elasticsearch8,今天的重点是SpringBoot应用连接带有安全检查的elastics 查看详情

[spring实战系列]配置springioc容器的bean

1.简介Spring提供了一个强大的IOC容器来管理组成应用的bean。为了利用容器服务,必须配置运行于SpringIOC容器中的Bean。2.解决方案你可以通过XML文件,属性文件,注释甚至API来设置SpringIOC容器中的Bean。Spring允许你在一个或者多个be... 查看详情

disruptor笔记之五:事件消费实战(代码片段)

...的基础操作(不用Disruptor类)事件消费知识点小结事件消费实战常见场景等待策略知识点补充(终篇)本篇概览本篇是《disruptor笔记》的第五篇,前文《disruptor笔记之四:事件消费知识点小结》从理论上梳理分析了独立消费和... 查看详情