spring4.1.8擴充套件實戰之五:改變bean的定義(BeanFactoryPostProcessor介面)
本章我們繼續實戰spring的擴充套件能力,通過自定義BeanFactoryPostProcessor介面的實現類,來對bean例項做一些控制;
BeanFactoryPostProcessor介面簡介
spring容器初始化時,從資源中讀取到bean的相關定義後,儲存在beanFactory的成員變數中(參考DefaultListableBeanFactory類的成員變數beanDefinitionMap),在例項化bean的操作就是依據這些bean的定義來做的,而在例項化之前,spring允許我們通過自定義擴充套件來改變bean的定義,定義一旦變了,後面的例項也就變了,而beanFactory後置處理器,即BeanFactoryPostProcessor
原始碼分析
一起來看看上述功能對應的原始碼,從AbstractApplicationContext類的refresh方法看起,這裡面對應著容器初始化的基本操作;
- 如下圖所示,紅框中的invokeBeanFactoryPostProcessors方法用來找出所有beanFactory後置處理器,並且呼叫這些處理器來改變bean的定義:
- 開啟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