Spring BeanPostProcessor(後置處理器)的用法
為瞭弄清楚Spring框架,我們需要分別弄清楚相關核心接口的作用,本文來介紹下BeanPostProcessor接口
BeanPostProcessor
該接口我們也叫後置處理器,作用是在Bean對象在實例化和依賴註入完畢後,在顯示調用初始化方法的前後添加我們自己的邏輯。註意是Bean實例化完畢後及依賴註入完成後觸發的。接口的源碼如下
public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
方法 | 說明 |
---|---|
postProcessBeforeInitialization | 實例化、依賴註入完畢, 在調用顯示的初始化之前完成一些定制的初始化任務 |
postProcessAfterInitialization | 實例化、依賴註入、初始化完畢時執行 |
一、自定義後置處理器演示
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那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } /** * 實例化、依賴註入、初始化完畢時執行 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } }
註意:接口中兩個方法不能返回null,如果返回null那麼在後續初始化方法將報空指針異常或者通過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實例化及屬性註入後執行的,且在自定義的初始化方法之前執行(通過init-method指定)。而postProcessAfterInitialization方法是在自定義初始化方法執行之後執行的。
註意!!!
BeanFactory和ApplicationContext兩個容器對待bean的後置處理器稍微有些不同。ApplicationContext容器會自動檢測Spring配置文件中那些bean所對應的Java類實現瞭BeanPostProcessor接口,並自動把它們註冊為後置處理器。在創建bean過程中調用它們,所以部署一個後置處理器跟普通的bean沒有什麼太大區別。
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那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } /** * 實例化、依賴註入、初始化完畢時執行 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } }
public class MyBeanPostProcessor2 implements BeanPostProcessor{ /** * 實例化、依賴註入完畢,在調用顯示的初始化之前完成一些定制的初始化任務 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } /** * 實例化、依賴註入、初始化完畢時執行 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 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,優先級最高,值越大優先級越低
public class MyBeanPostProcessor implements BeanPostProcessor,Ordered{ /** * 實例化、依賴註入完畢,在調用顯示的初始化之前完成一些定制的初始化任務 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } /** * 實例化、依賴註入、初始化完畢時執行 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } @Override public int getOrder() { // TODO Auto-generated method stub return 10; } }
public class MyBeanPostProcessor2 implements BeanPostProcessor,Ordered{ /** * 實例化、依賴註入完畢,在調用顯示的初始化之前完成一些定制的初始化任務 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 return bean; } /** * 實例化、依賴註入、初始化完畢時執行 * 註意:方法返回值不能為null * 如果返回null那麼在後續初始化方法將報空指針異常或者通過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不同執行不同的處理操作 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
數值越大的優先級越低,所以A的輸出就在後面瞭。
對BeanPostProcessor接口的理解
今天想起來寫一篇,是因為自己糾正瞭對BeanPostProcessor接口的理解誤區,寫文章往往都是源於這種豁然開朗的靈感。不過今天又是孤陋寡聞的一天呢,一個知識點理解錯瞭這麼長時間居然都不自知。
Bean的生命周期應該都很清楚,先貼這張圖
這張Bean生命周期順序圖裡大部分環節都是比較好理解的,比如setBeanName和setBeanFactory包括setApplicationContext,都是實現瞭對應的接口,就可以在實例化這個Bean的時候為這個Bean設置BeanName,或者註入BeanFactory和ApplicationContext,可以用於獲取IOC容器中的其他Bean等。但是實現瞭BeanPostProcessor接口不能按這個邏輯去理解,
先看一下實現BeanPostProcessor接口後的重寫哪兩個方法:
之前我一直是按這個理解邏輯去理解實現瞭BeanPostProcessor接口,理解誤區是:MyBeanPost這個類實現瞭BeanPostProcessor接口,實例化的MyBeanPost的時候就會去調用該類裡重寫的postProcessBeforeInitialization()和postProcessAfterInitialization()方法,這兩個方法裡拿到的beanName就是@Component裡定義的”myBeanPost”,Object類型的bean就是MyBeanPost對象,然後實例化MyBeanPost對象前後去在這兩個方法裡做點什麼。
之前一直是這麼理解的,其實一直是有疑惑的,因為按這麼理解,postProcessBeforeInitialization()方法能做的在自定義的@PostConstruct方法裡也能做,那這兩個就區分不開瞭。雖然有疑惑但自己也沒有去試過,直到今天項目開發的時候真的想用postProcessAfterInitialization()方法去在初始化完一個Bean的時候註入點東西的時候,一試傻眼瞭。
直接貼上圖那種寫法時的啟動日志:
如果按我之前那麼理解,這裡應該隻打印出”myBeanPost執行瞭postProcessBeforeInitialization”和”myBeanPost執行瞭postProcessAfterInitialization”才對啊,居然打印出瞭這麼多,而且我全局搜瞭一下,偏偏沒有beanName是”myBeanPost”的日志記錄。這個時候我才知道之前我一直理解錯瞭,於是重視起來開始找原因。
Spring的源碼一頓翻之後找到瞭Spring初始化Bean的一段代碼:
這裡的invokeInitMethods()就是反射調用我們自定義的初始化方法,即順序圖中的第八步,可以清楚的看到applyBeanPostProcessorsBeforeInitialization()方法在前,applyBeanPostProcessorsAfterInitialization()方法在後,這似乎也和順序圖中執行postProcessBeforeInitialization()在執行自定義初始化方法前,執行postProcessAfterInitialization()在後對應上瞭,繼續點進去看,
先看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容器中,沒放進去當然拿不到瞭,自然也不會去執行這個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。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Spring源碼解析之BeanPostProcessor知識總結
- 詳解Spring中Bean後置處理器(BeanPostProcessor)的使用
- 關於Spring BeanPostProcessor的執行順序
- Spring BeanPostProcessor源碼示例解析
- 基於spring boot排除掃描類的三種方式小結