欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring BeanPostProcessor(后置處理器)的用法

 更新時間:2021年10月13日 16:56:14   作者:波波烤鴨  
這篇文章主要介紹了Spring BeanPostProcessor(后置處理器)的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

為了弄清楚Spring框架,我們需要分別弄清楚相關核心接口的作用,本文來介紹下BeanPostProcessor接口

BeanPostProcessor

該接口我們也叫后置處理器,作用是在Bean對象在實例化和依賴注入完畢后,在顯示調用初始化方法的前后添加我們自己的邏輯。注意是Bean實例化完畢后及依賴注入完成后觸發(fā)的。接口的源碼如下

public interface BeanPostProcessor { 
 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
方法 說明
postProcessBeforeInitialization 實例化、依賴注入完畢,
在調用顯示的初始化之前完成一些定制的初始化任務
postProcessAfterInitialization 實例化、依賴注入、初始化完畢時執(zhí)行

一、自定義后置處理器演示

1.自定義處理器

package com.dpb.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
 * 自定義BeanPostProcessor實現類
 * BeanPostProcessor接口的作用是:
 *   我們可以通過該接口中的方法在bean實例化、配置以及其他初始化方法前后添加一些我們自己的邏輯
 * @author dengp
 *
 */
public class MyBeanPostProcessor implements BeanPostProcessor{
 /**
  * 實例化、依賴注入完畢,在調用顯示的初始化之前完成一些定制的初始化任務
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("初始化 before--實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 /**
  * 實例化、依賴注入、初始化完畢時執(zhí)行 
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("初始化 after...實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
}

注意:接口中兩個方法不能返回null,如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象,因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中

2.Pojo類

public class User {
 private int id;
 
 private String name;
 
 private String beanName;
 
 public User(){
  System.out.println("User 被實例化");
 }
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  System.out.println("設置:"+name);
  this.name = name;
 }
 public String getBeanName() {
  return beanName;
 }
 public void setBeanName(String beanName) {
  this.beanName = beanName;
 }
 /**
  * 自定義的初始化方法
  */
 public void start(){
  System.out.println("User 中自定義的初始化方法");
 }
}

3.配置文件注冊

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.dpb.pojo.User" id="user" init-method="start">
  <property name="name" value="波波烤鴨" />
 </bean>
 
 <!-- 注冊處理器 -->
 <bean class="com.dpb.processor.MyBeanPostProcessor"></bean>
</beans>

4.測試

 @Test
public void test() {
 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
 User user = ac.getBean(User.class);
 System.out.println(user);
}

輸出結果

User 被實例化
設置:波波烤鴨
初始化 before--實例化的bean對象:com.dpb.pojo.User@65e2dbf3 user
User 中自定義的初始化方法
初始化 after...實例化的bean對象:com.dpb.pojo.User@65e2dbf3 user
com.dpb.pojo.User@65e2dbf3 

通過輸出語句我們也能看到postProcessBeforeInitialization方法的輸出語句是在Bean實例化及屬性注入后執(zhí)行的,且在自定義的初始化方法之前執(zhí)行(通過init-method指定)。而postProcessAfterInitialization方法是在自定義初始化方法執(zhí)行之后執(zhí)行的。

注意?。?!

BeanFactory和ApplicationContext兩個容器對待bean的后置處理器稍微有些不同。ApplicationContext容器會自動檢測Spring配置文件中那些bean所對應的Java類實現了BeanPostProcessor接口,并自動把它們注冊為后置處理器。在創(chuàng)建bean過程中調用它們,所以部署一個后置處理器跟普通的bean沒有什么太大區(qū)別。

BeanFactory容器注冊bean后置處理器時必須通過代碼顯示的注冊,在IoC容器繼承體系中的ConfigurableBeanFactory接口中定義了注冊方法

/**
 * Add a new BeanPostProcessor that will get applied to beans created
 * by this factory. To be invoked during factory configuration.
 * <p>Note: Post-processors submitted here will be applied in the order of
 * registration; any ordering semantics expressed through implementing the
 * {@link org.springframework.core.Ordered} interface will be ignored. Note
 * that autodetected post-processors (e.g. as beans in an ApplicationContext)
 * will always be applied after programmatically registered ones.
 * @param beanPostProcessor the post-processor to register
 */
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

測試代碼如下

@Test
public void test2() {
 //ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
 XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
 // 顯示添加后置處理器
 bf.addBeanPostProcessor(bf.getBean(MyBeanPostProcessor.class));
 User user = bf.getBean(User.class);
 System.out.println(user);
}

二、多個后置處理器

我們可以在Spring配置文件中添加多個BeanPostProcessor(后置處理器)接口實現類,在默認情況下Spring容器會根據后置處理器的定義順序來依次調用。

public class MyBeanPostProcessor implements BeanPostProcessor{
 /**
  * 實例化、依賴注入完畢,在調用顯示的初始化之前完成一些定制的初始化任務
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A before--實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 /**
  * 實例化、依賴注入、初始化完畢時執(zhí)行 
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A after...實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
}
public class MyBeanPostProcessor2 implements BeanPostProcessor{
 /**
  * 實例化、依賴注入完畢,在調用顯示的初始化之前完成一些定制的初始化任務
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B before--實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 /**
  * 實例化、依賴注入、初始化完畢時執(zhí)行 
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B after...實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
}

配置文件注冊

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.dpb.pojo.User" id="user" init-method="start">
  <property name="name" value="波波烤鴨" />
 </bean>
 
 <!-- 注冊處理器 -->
 <bean class="com.dpb.processor.MyBeanPostProcessor"/>
 <bean class="com.dpb.processor.MyBeanPostProcessor2"/>
</beans>

測試結果

User 被實例化
設置:波波烤鴨
A before--實例化的bean對象:com.dpb.pojo.User@7fac631b user
B before--實例化的bean對象:com.dpb.pojo.User@7fac631b user
User 中自定義的初始化方法
A after...實例化的bean對象:com.dpb.pojo.User@7fac631b user
B after...實例化的bean對象:com.dpb.pojo.User@7fac631b user
com.dpb.pojo.User@7fac631b

三、顯示指定順序

在Spring機制中可以指定后置處理器調用順序,通過讓BeanPostProcessor接口實現類實現Ordered接口getOrder方法,該方法返回一整數,默認值為 0,優(yōu)先級最高,值越大優(yōu)先級越低

public class MyBeanPostProcessor implements BeanPostProcessor,Ordered{
 /**
  * 實例化、依賴注入完畢,在調用顯示的初始化之前完成一些定制的初始化任務
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A before--實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 /**
  * 實例化、依賴注入、初始化完畢時執(zhí)行 
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異?;蛘咄ㄟ^getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("A after...實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 @Override
 public int getOrder() {
  // TODO Auto-generated method stub
  return 10;
 }
}
public class MyBeanPostProcessor2 implements BeanPostProcessor,Ordered{
 /**
  * 實例化、依賴注入完畢,在調用顯示的初始化之前完成一些定制的初始化任務
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異常或者通過getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B before--實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 /**
  * 實例化、依賴注入、初始化完畢時執(zhí)行 
  * 注意:方法返回值不能為null
  * 如果返回null那么在后續(xù)初始化方法將報空指針異常或者通過getBean()方法獲取不到bena實例對象
  * 因為后置處理器從Spring IoC容器中取出bean實例對象沒有再次放回IoC容器中
  */
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  System.out.println("B after...實例化的bean對象:"+bean+"\t"+beanName);
  // 可以根據beanName不同執(zhí)行不同的處理操作
  return bean;
 }
 @Override
 public int getOrder() {
  // TODO Auto-generated method stub
  return 2;
 }
}

測試輸出結果

User 被實例化
設置:波波烤鴨
B before--實例化的bean對象:com.dpb.pojo.User@7fac631b user
A before--實例化的bean對象:com.dpb.pojo.User@7fac631b user
User 中自定義的初始化方法
B after...實例化的bean對象:com.dpb.pojo.User@7fac631b user
A after...實例化的bean對象:com.dpb.pojo.User@7fac631b user
com.dpb.pojo.User@7fac631b

數值越大的優(yōu)先級越低,所以A的輸出就在后面了。

對BeanPostProcessor接口的理解

今天想起來寫一篇,是因為自己糾正了對BeanPostProcessor接口的理解誤區(qū),寫文章往往都是源于這種豁然開朗的靈感。不過今天又是孤陋寡聞的一天呢,一個知識點理解錯了這么長時間居然都不自知。

Bean的生命周期應該都很清楚,先貼這張圖

這張Bean生命周期順序圖里大部分環(huán)節(jié)都是比較好理解的,比如setBeanName和setBeanFactory包括setApplicationContext,都是實現了對應的接口,就可以在實例化這個Bean的時候為這個Bean設置BeanName,或者注入BeanFactory和ApplicationContext,可以用于獲取IOC容器中的其他Bean等。但是實現了BeanPostProcessor接口不能按這個邏輯去理解,

先看一下實現BeanPostProcessor接口后的重寫哪兩個方法:

之前我一直是按這個理解邏輯去理解實現了BeanPostProcessor接口,理解誤區(qū)是:MyBeanPost這個類實現了BeanPostProcessor接口,實例化的MyBeanPost的時候就會去調用該類里重寫的postProcessBeforeInitialization()和postProcessAfterInitialization()方法,這兩個方法里拿到的beanName就是@Component里定義的"myBeanPost",Object類型的bean就是MyBeanPost對象,然后實例化MyBeanPost對象前后去在這兩個方法里做點什么。

之前一直是這么理解的,其實一直是有疑惑的,因為按這么理解,postProcessBeforeInitialization()方法能做的在自定義的@PostConstruct方法里也能做,那這兩個就區(qū)分不開了。雖然有疑惑但自己也沒有去試過,直到今天項目開發(fā)的時候真的想用postProcessAfterInitialization()方法去在初始化完一個Bean的時候注入點東西的時候,一試傻眼了。

直接貼上圖那種寫法時的啟動日志:

如果按我之前那么理解,這里應該只打印出"myBeanPost執(zhí)行了postProcessBeforeInitialization"和"myBeanPost執(zhí)行了postProcessAfterInitialization"才對啊,居然打印出了這么多,而且我全局搜了一下,偏偏沒有beanName是"myBeanPost"的日志記錄。這個時候我才知道之前我一直理解錯了,于是重視起來開始找原因。

Spring的源碼一頓翻之后找到了Spring初始化Bean的一段代碼:

這里的invokeInitMethods()就是反射調用我們自定義的初始化方法,即順序圖中的第八步,可以清楚的看到applyBeanPostProcessorsBeforeInitialization()方法在前,applyBeanPostProcessorsAfterInitialization()方法在后,這似乎也和順序圖中執(zhí)行postProcessBeforeInitialization()在執(zhí)行自定義初始化方法前,執(zhí)行postProcessAfterInitialization()在后對應上了,繼續(xù)點進去看,

先看applyBeanPostProcessorsBeforeInitialization()方法

需要注意這里existingBean參數是正在實例化的Bean,這里的getBeanPostProcessors()方法是去拿所有實現了BeanPostProcessor接口的實現類的Bean,然后再調用BeanPostProcessor接口實現類的postProcessBeforeInitialization()方法。

看到這里就推翻了我之前的理解了,原來一個類實現了BeanPostProcessor接口,那重寫的兩個方法不是實例化該類的時候調用的,而是容器實例化其他Bean的時候調用的,容器會找出當前容器中所有實現了BeanPostProcessor接口實現類對象,然后一個遍歷一個一個調用,這也是為什么上圖打印出來的日志會有這么多BeanName的日志記錄。也就是說如果容器需要實例化N個Bean,同時容器中已有M個BeanPostProcessor接口實現類對象,那BeanPostProcessor接口的那兩個方法就會被調用N*M次,雖然是在不同的實現類中調用的。

applyBeanPostProcessorsAfterInitialization()同理:

一樣的,總結一下,對于BeanPostProcessor接口的理解的理解應該是這樣:

BeanPostProcessor接口有兩個方法,分別為Bean容器中每個對象被實例化前和實例化后調用,即交給Bean容器管理的所有對象,比如打了@Component,@RestController等注解的類,程序啟動時就會去實例化并放到Bean容器中的這些類。每次實例化一個Bean都會調用BeanPostProcessor接口重寫方法,所有那兩個方法是被多次調用的。應該在那兩個方法中很據BeanName拿到自己想處理的Bean實例,再去做對應處理。

文章開頭也提到,日志打印了這么多,偏偏沒有beanName是"myBeanPost"的日志記錄,也就是說,BeanPostProcessor接口的實現類明明也打了@Component注解,可是為啥在自己的重寫方法中打印不出來beanName是自己的日志記錄呢?這是正常而且必然的。因為雖然MyBeanPost也打了@Component,程序啟動時也會去實例化這個Bean,但是實例化它的時候,getBeanPostProcessors()方法是拿不到MyBeanPost自己這個Bean的。還沒實例化完呢怎么放進Bean容器中,沒放進去當然拿不到了,自然也不會去執(zhí)行這個Bean里重寫的方法了。

不過如果另外寫一個BeanPostProcessor接口的實現類就不一定了,如果實例化MyBeanPost的時候另一個BeanPostProcessor實現類已經被實例化好了放進Bean容器中了,getBeanPostProcessors()就能拿到,然后在另一個BeanPostProcessor實現類里重寫的方法里打印出beanName為"myBeanPost"的日志記錄。反正,BeanPostProcessor實現類不能在自己重寫的方法中不能拿到自己的Bean實例。

所以BeanPostProcessor接口的正確用法應該是寫一個MyBeanPostProcessor實現類,意思是自定義的Bean處理類,然后在這個自定義的Bean處理類中根據beanName去篩選拿到Bean容器中的其他Bean,做一些實例化這些Bean之前之后的邏輯。例如這樣:

要注意重寫的方法默認是返回null的,這里要返回處理后的bean,如果返回null就會是處理之前的bean,這個邏輯在applyBeanPostProcessorsBeforeInitialization()方法和applyBeanPostProcessorsAfterInitialization()方法里,

之前貼的圖里有,再貼一遍:

current即自定義處理之后的,如果是null,還是返回result。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • springboot項目中后端接收前端傳參的方法示例詳解

    springboot項目中后端接收前端傳參的方法示例詳解

    這篇文章主要介紹了springboot項目中一些后端接收前端傳參的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • java發(fā)送http get請求的兩種方法(總結)

    java發(fā)送http get請求的兩種方法(總結)

    下面小編就為大家?guī)硪黄猨ava發(fā)送http get請求的兩種方法(總結)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • java連接zookeeper實現zookeeper教程

    java連接zookeeper實現zookeeper教程

    這篇文章主要介紹了java連接zookeeper實現zookeeper教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java實現猜數程序

    Java實現猜數程序

    這篇文章主要為大家詳細介紹了Java實現猜數程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • 如何使用Logback日志保存到相對路徑

    如何使用Logback日志保存到相對路徑

    在使用Logback中需要保存輸出日志,但是卻在保存的時候路徑出現問題,下面通過案例代碼講解如何使用Logback日志保存到相對路徑,感興趣的朋友一起看看吧
    2025-05-05
  • 解決mybatis一對多關聯查詢多條數據只顯示一條的問題

    解決mybatis一對多關聯查詢多條數據只顯示一條的問題

    這篇文章主要介紹了解決mybatis一對多關聯查詢多條數據只顯示一條的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Java多線程之簡單模擬售票功能

    Java多線程之簡單模擬售票功能

    這篇文章主要介紹了Java多線程之簡單模擬售票功能,文中有非常詳細的代碼示例,對正在學習java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-04-04
  • springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現

    springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現

    本文主要介紹了springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • SpringAOP實現日志收集管理功能(步驟詳解)

    SpringAOP實現日志收集管理功能(步驟詳解)

    這篇文章主要介紹了SpringAOP實現日志收集管理功能,本文分步驟通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • Java泛型變量如何添加約束

    Java泛型變量如何添加約束

    這篇文章主要介紹了Java泛型變量如何添加約束,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-05-05

最新評論