1. 程式人生 > >spring4.1.8擴充套件實戰之五:改變bean的定義(BeanFactoryPostProcessor介面)

spring4.1.8擴充套件實戰之五:改變bean的定義(BeanFactoryPostProcessor介面)

本章我們繼續實戰spring的擴充套件能力,通過自定義BeanFactoryPostProcessor介面的實現類,來對bean例項做一些控制;

BeanFactoryPostProcessor介面簡介

spring容器初始化時,從資源中讀取到bean的相關定義後,儲存在beanFactory的成員變數中(參考DefaultListableBeanFactory類的成員變數beanDefinitionMap),在例項化bean的操作就是依據這些bean的定義來做的,而在例項化之前,spring允許我們通過自定義擴充套件來改變bean的定義,定義一旦變了,後面的例項也就變了,而beanFactory後置處理器,即BeanFactoryPostProcessor

就是用來改變bean定義的;

原始碼分析

一起來看看上述功能對應的原始碼,從AbstractApplicationContext類的refresh方法看起,這裡面對應著容器初始化的基本操作;

  1. 如下圖所示,紅框中的invokeBeanFactoryPostProcessors方法用來找出所有beanFactory後置處理器,並且呼叫這些處理器來改變bean的定義:
    這裡寫圖片描述

  2. 開啟invokeBeanFactoryPostProcessors方法,如下所示,實際操作是委託PostProcessorRegistrationDelegate去完成的:
protected void invokeBeanFactoryPostProcessors
(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); }


3. 在呼叫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);
}


4. AbstractApplicationContext.addBeanFactoryPostProcessor方法是留給業務擴充套件時呼叫的,例如在springboot初始化時,ConfigurationWarningsApplicationContextInitializer類的initialize方法中就有呼叫:

@Override
public void initialize(ConfigurableApplicationContext context) {
    context.addBeanFactoryPostProcessor(
            new ConfigurationWarningsPostProcessor(getChecks()));
}


5. 看過了如何新增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方法;

6. 從上面的分析可以發現,所有實現了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下載原始碼,地址和連結資訊如下表所示:

名稱 連結 備註
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該專案原始碼的倉庫地址,ssh協議

這個git專案中有多個資料夾,本章原始碼在資料夾customizebeanfactorypostprocessor下,如下圖紅框所示:
這裡寫圖片描述

接下來開始實戰吧:
1. 基於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>


2. 定義一個服務介面CalculateService:

package com.bolingcavalry.customizebeanfactorypostprocessor.service;

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

    /**
     * 返回當前實現類的描述資訊
     * @return
     */
    String getServiceDesc();
}


3. 建立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;
    }
}


4. 建立一個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”;

5. 建立啟動類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);
    }
}


6. 啟動應用,瀏覽器輸入地址:http://localhost:8080/add/1/2,看到的響應如下圖,紅框中就是CustomizeBeanFactoryPostProcessor對名為calculateService的bean的定義物件修改後導致的結果:
這裡寫圖片描述

至此,BeanFactoryPostProcessor的原始碼分析和擴充套件實戰就結束了,通過本次實戰,除了對spring擴充套件的認識加深,又掌握了一種控制bean的方式;

相關推薦

spring4.1.8擴充套件實戰改變bean定義(BeanFactoryPostProcessor介面)

本章我們繼續實戰spring的擴充套件能力,通過自定義BeanFactoryPostProcessor介面的實現類,來對bean例項做一些控制; BeanFactoryPostProcessor介面簡介 spring容器初始化時,從資源中讀取到bea

spring4.1.8擴充套件實戰Aware介面揭祕

接下來通過分析spring原始碼,我們來看看典型的Aware子類有哪些,使用場景是什麼? 在spring容器初始化過程中,會執行AbstractApplicationContext類的prepareBeanFactory方法,這裡面會建立一個bean後置處理器

spring4.1.8擴充套件實戰感知spring容器變化(SmartLifecycle介面)

本章由以下幾部分組成: SmartLifecycle介面概覽; spring容器啟動與SmartLifecycle的關係; spring容器關閉與SmartLifecycle的關係; 關於Lifecycle和SmartLifecycle; 實戰SmartLi

spring4.1.8擴充套件實戰之一定義環境變數驗證

為了方便開發和測試,我們的擴充套件實戰是在SpringBoot框架下進行的,在SpringBoot自定義spring擴充套件的方式請參考《SpringBoot應用使用自定義的ApplicationContext實現類》 擴充套件功能介紹 今天實戰的內容,是通過

Flink處理函式實戰CoProcessFunction(雙流處理)

### 歡迎訪問我的GitHub [https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) 內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等; ##

MyBatis初級實戰一對一關聯查詢

### 歡迎訪問我的GitHub [https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) 內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等; ### 本

碼農裝13寶典系列Ubuntu自定義字型縮放級別

目前主流顯示器都有一個很高的解析度,而使用預設的解析度會使字型顯示過小,單純地調整解析度又容易讓字看起來發虛。 系統提供了一個字型縮放級別調整的功能。Windows初始化時就已經為使用者設定好了,而Ubuntu只有兩個選項:100%、200%,顯然不能滿足需求。 那怎麼辦? 這裡需要

spring4.1.8初始化原始碼學習三部曲setConfigLocations方法

本章是學習spring4.1.8初始化原始碼的第二篇,前一章《spring4.1.8初始化原始碼學習三部曲之一:AbstractApplicationContext構造方法》對AbstractApplicationContext的初始化做了分析,本章我們聚焦

spring4.1.8初始化原始碼學習三部曲AbstractApplicationContext.refresh方法

本章是《spring4.1.8初始化原始碼學習三部曲》系列的終篇,重點是學習AbstractApplicationContext類的refresh()方法; 我們先回顧ClassPathXmlApplicationContext類的初始化過程如下程式碼:

設計模式實戰應用工廠方法模式

fontsize -c iterator name 工廠方法 iss sat cep exce 工廠方法模式的定義 工廠方法模式的應用相當廣泛。工廠方法模式在 Java API 中的應用比比皆是:java.util.Collection 接

spring4.1.8初始化原始碼學習三部曲之一AbstractApplicationContext構造方法

這個demo的原始碼可以在github下載,地址和連結資訊如下表所示: 名稱 連結 備註 專案主頁 https://github.com/zq2599/blog_demos 該專案在GitHub上的主頁 git倉庫地址(https)

noi題庫1.8程式設計基礎多維陣列題解大禮包20180918

題目傳送門 以下是對noi題庫1.8的全部題解,有需要的同學請自行學習。 有任何錯漏或者疑問,請留言。謝謝~~~ 1、題目分析 2、程式碼截圖 1.8程式設計基礎之多為陣列 編號 題目 相對難度1-5 知識點 1 矩陣交換行 2

MongoDB實戰系列mongodb的分片配置

md01    10.0.0.11 md02    10.0.0.12 md03    10.0.0.14 2、啟動三臺機器的mongod例項 根據Replica Set、Sharding策略部署mongod。將兩個sharding組部署到三臺伺服器上,每個sharding

Entity Framework 4.1 多對多的關係

原文名稱:Entity Framework 4.1: Many to Many Relationships (5) 這篇文章討論多對多的關係。 讓我們從最簡單的例子開始。我們讓 EF4.1 來推斷表的對映。我在訂單和僱員之間建模多對多的關係。 publicclass Order {

Zend Framework 1.10.1 快速入門建立一個表單

為了使我們的留言本有用,我們需要一個可以提交新條目的表單。 我們第一件事是要建立事實表單的類。為了建立空的表單類,執行:       % zf create form Guestbook       Creating a form at application/form

Keil C51對C語言的關鍵詞擴充套件 code

code 指定儲存位置位於程式儲存器。程式儲存器只讀,因此code型別的變數,是無法再次賦值的。 unsigned char code ary[ ] = :"Read only"; /* 陣列ary位於程式儲存器 */ ary[0]='a'; /* 錯誤,不可修改*/

SCCM2012 R2實戰系列發現方法

ref http cto 客戶 gif color 返回 space int 打開SCCM2012的控制臺 點擊左側欄的“管理”選項,然後展開“層次結構配置”,點擊“發現方法”來配置客戶端發現。

VCSA 6.5 HA 配置故障轉移測試

center style justify 接著上篇文章配置完成VCSA的高可用後,其是否真的能實現高可用的效果,本篇文章將會一探究竟手動故障切換在vCenter HA配置頁面可以看到當前的主動節點、被動節點和見證節點;在例行維護或者其他時候可以手動執行故障切換通過右上方的"啟動故障切換" ,在一般

OneNET麒麟座應用開發獲取加速度傳感器ADXL345數據

命令 多個 data lag 基本 采集 .cn 端口 成了 由於數據采集站基本都安裝在野外或者樓頂,安裝位置以及震動對檢測數據的準確性有一定影響。所以想要有一個位置狀態數據,正好發現麒麟作上有ADXL345,這樣一個數字輸出的加速度傳感器。如圖中紅框所示: 1、ADXL

LIVE555研究RTPServer(二)

tpch live555 循環調用 family 每一個 函數 計算 ack close LIVE555研究之五:RTPServer(二) 接上文,main函數的幾行代碼創建了RTSPSe