原文:談談Spring中的BeanPostProcessor介面
作者:特務依昂
一. 前言
這幾天正在複習Spring
的相關內容,在瞭解bean
的生命週期的時候,發現其中涉及到一個特殊的介面——BeanPostProcessor
介面。由於網上沒有找到比較好的部落格,所有最後花了好幾個小時,透過Spring
的官方文件對它做了一個大致的瞭解,下面就來簡單介紹一下這個介面。
二. 正文
2.1 BeanPostProcessor的功能
有時候,我們希望Spring
容器在建立bean
的過程中,能夠使用我們自己定義的邏輯,對建立的bean
做一些處理,或者執行一些業務。而實現方式有多種,比如自定義bean
的初始化話方法等,而BeanPostProcessor
介面也是用來實現類似的功能的。
如果我們希望容器中建立的每一個bean
,在建立的過程中可以執行一些自定義的邏輯,那麼我們就可以編寫一個類,並讓他實現BeanPostProcessor
介面,然後將這個類註冊到一個容器中。容器在建立bean
的過程中,會優先建立實現了BeanPostProcessor
介面的bean
,然後,在建立其他bean
的時候,會將建立的每一個bean
作為引數,呼叫BeanPostProcessor
的方法。而BeanPostProcessor
介面的方法,即是由我們自己實現的。下面就來具體介紹一下BeanPostProcessor
的使用。
2.2 BeanPostProcessor的使用
我們先看一看BeanPostProcessor
介面的程式碼:
public interface BeanPostProcessor { // 注意這個方法名稱關鍵的是before這個單詞 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; // 注意這個方法名稱關鍵的是after這個單詞 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
可以看到,BeanPostProcessor
介面只有兩個抽象方法,由實現這個介面的類去實現(後面簡稱這兩個方法為before
和after
),這兩個方法有著相同的引數:
- bean:容器正在建立的那個
bean
的引用; - beanName:容器正在建立的那個
bean
的名稱;
那這兩個方法何時執行呢?這就涉及到Spring
中,bean
的生命週期了。下面引用《Spring實戰》
中的一張圖,這張圖表現了bean
的生命週期,而Spring
容器建立bean
的具體過程,請參考這篇部落格——簡單談談Spring的IoC。
上圖中標紅的兩個地方就是BeanPostProcessor
中兩個方法的執行時機。Spring
容器在建立bean
時,如果容器中包含了BeanPostProcessor
的實現類物件,那麼就會執行這個類的這兩個方法,並將當前正在建立的bean
的引用以及名稱作為引數傳遞進方法中。這也就是說,BeanPostProcessor
的作用域是當前容器中的所有bean
(不包括一些特殊的bean
,這個後面說)。
值得注意的是,我們可以在一個容器中註冊多個不同的BeanPostProcessor
的實現類物件,而bean
在建立的過程中,將會輪流執行這些物件實現的before
和after
方法。那執行順序如何確定呢?Spring
提供了一個介面Ordered
,我們可以讓BeanPostProcessor
的實現類實現這個Ordered
介面,並實現介面的getOrder
方法。這個方法的返回值是一個int
型別,Spring
容器會透過這個方法的返回值,對容器中的多個BeanPostProcessor
物件進行從小到大排序,然後在建立bean
時依次執行它們的方法。也就是說,getOrder
方法返回值越小的BeanPostProcessor
物件,它的方法將越先被執行。
2.3 一個簡單的demo
下面就來寫一個簡單的demo
,來看看BeanPostProcessor
的效果。首先定義兩個普通的bean
,就叫User
和Car
吧:
public class User { private String name; private int age; // ... 省略getter和setter... } public class Car { private int speed; private double price; // ... 省略getter和setter... }
在定義一個BeanPostProcessor
的實現類,重寫介面的方法:
public class PostBean implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 輸出資訊,方便我們看效果 System.out.println("before -- " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 輸出資訊,方便我們看效果 System.out.println("after -- " + beanName); return bean; } }
我們直接使用一個Java
類作為Spring
的配置,就不使用xml
配置檔案了。配置如下,在這個配置類中,宣告瞭User
、Car
以及PostBean
這三個bean
的工廠方法,前兩個是普通bean
,而PostBean
是實現BeanPostProcessor
的bean
:
@Configuration public class BeanConfig { // 在Spring中註冊User這個bean @Bean public User user() { return new User(); } // 在Spring中註冊Car這個bean @Bean public Car car() { return new Car(); } // 在Spring中註冊PostBean這個bean,這個bean實現了BeanPostProcessor介面 @Bean public PostBean postBean() { return new PostBean(); } }
好,有了上面四個類,就可以開始測試了,下面是測試方法:
@Test public void testConfig() { ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class); }
上面這個方法啥也不幹,就是建立一個Spring
的上下文物件,也就是Spring
的IoC
容器。這個容器將去載入BeanConfig
這個類的配置,然後建立配置類中宣告的物件。在建立User和Car的過程中,就會執行BeanPostProcessor
實現類的方法。我們看看執行結果:
before -- org.springframework.context.event.internalEventListenerProcessor after -- org.springframework.context.event.internalEventListenerProcessor before -- org.springframework.context.event.internalEventListenerFactory after -- org.springframework.context.event.internalEventListenerFactory before -- car after -- car before -- user after -- user
可以看到,BeanPostProcessor
的before
方法和after
方法都被呼叫了四次,最後兩次呼叫時,傳入的引數正是我們自己定義的Bean
——User
和Car
。那為什麼呼叫了四次呢,明明我們只定義了兩個普通bean
。我們看上面的輸出發現,前兩次呼叫,傳入的bean
是Spring
內部的元件。Spring
在初始化容器的過程中,會建立一些自己定義的bean
用來實現一些功能,而這些bean
,也會執行我們註冊進容器中的BeanPostProcessor
實現類的方法。
2.4 使用BeanPostProcessor時容易踩的坑
BeanPostProcessor
這個介面,在使用的過程中,其實還有許多的限制和坑點,若不瞭解的話,可能會讓你對某些結果感到莫名其妙。下面我就來簡單地說一說:
(一)BeanPostProcessor依賴的bean,不會執行BeanPostProcessor的方法
當我們在BeanPostProcessor
的實現類中,依賴了其他的bean
,那麼被依賴的bean
被建立時,將不會執行它所在的BeanPostProcessor
實現類實現的方法,比如我們修改PostBean
的實現,如下所示:
@Component public class PostBean implements BeanPostProcessor, Ordered { // 讓PostBean依賴User @Autowired private User user; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
此時,容器在建立User
這個bean
時,不會執行PostBean
實現的兩個方法,因為由於PostBean
依賴於user
,所以user
需要在PostBean
之前建立完成,這也就意味著在user
建立時,PostBean
還未初始化完成,所以不會呼叫它的方法。
(二)BeanPostProcessor以及依賴的bean無法使用AOP
以下是Spring
官方文件中的一段話:
Because AOP auto-proxying is implemented as a
BeanPostProcessor
itself, neitherBeanPostProcessor
s nor the beans they reference directly are eligible for auto-proxying, and thus do not have aspects woven into them.
上面這段話的意思大致是說,Spring
的AOP
代理就是作為BeanPostProcessor
實現的,所以我們無法對BeanPostProcessor的實現類使用AOP織入通知,也無法對BeanPostProcessor的實現類依賴的bean使用AOP織入通知。Spring
的AOP
實現我暫時還沒有研究過,所以上面的說AOP
作為BeanPostProcessor
實現的意思我不是特別明白,但是我們現在只需要關注BeanPostProcessor
以及它依賴的bean
都無法使用AOP
這一點。為了驗證上面的說法,我稍微修改一下2.3
中的例子,來測試一波。
首先,我們修改2.3
中用到的PostBean
和User
這兩個類,讓PostBean
依賴User
這個類,同時為了輸出更加地簡單,我們將before
和after
方法中的println
語句刪了:
@Component public class PostBean implements BeanPostProcessor, Ordered { // 讓PostBean依賴User @Autowired private User user; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } // 此方法用來測試AOP,作為切點 public void testAOP() { System.out.println("Post Bean"); } } @Component public class User { private String name; private int age; // ... 省略getter和setter... // 此方法用來測試AOP,用作切點 public void testAOP() { System.out.println("user bean"); } }
然後,我們定義一個AOP
的切面,在切面中將PostBean
的testAOP
方法作為切點,程式碼如下:
@Aspect public class BeanPostProcessorAspect { // 此方法織入PostBean的testAOP方法 @Before("execution(* cn.tewuyiang.pojo.PostBean.testAOP(..))") public void before() { System.out.println("before1"); } // 此方法織入User的testAOP方法 @Before("execution(* cn.tewuyiang.pojo.User.testAOP(..))") public void before2() { System.out.println("before2"); } }
好,這就準備完畢,可以開始測試了。我們這次使用Spring
註解掃描來配置bean
以及為bean
注入依賴,測試程式碼如下:
@Test public void testConfig() { ApplicationContext context = new AnnotationConfigApplicationContext(AutoConfig.class); // 獲取User這個bean,執行測試AOP的方法 User user = context.getBean(User.class); user.testAOP(); // 獲取PostBean這個bean,執行測試AOP的方法 PostBean bean = context.getBean(PostBean.class); bean.testAOP(); } 輸出如下: user bean post Bean
從輸出中可以看到,使用AOP
織入的前置通知沒有執行,這也就驗證了上面所說的,BeanPostProcessor
的實現類以及實現類依賴的bean
,無法使用AOP
為其織入通知。但是這個限制具體有到什麼程度,我也不是很確定,因為我使用xml
配置依賴,以及上面使用註解掃描兩種方式,AOP
織入都沒法使用,但是我在使用@Bean
這種配置方式時,被依賴的bean
卻成功執行了通知。所以,關於此處提到的限制,還需要深入瞭解Spring
容器的原始碼實現才能下定論。
(三)註冊BeanPostProcessor的方式以及限制
我們如何將BeanPostProcessor
註冊到Spring
容器中?方式主要有兩種,第一種就是上面一直在用的,將其宣告在Spring
的配置類或xml
檔案中,作為普通的bean
,讓ApplicationContext
物件去載入它,這樣它就被自動註冊到容器中了。而且Spring
容器會對BeanPostProcessor
的實現類做特殊處理,即會將它們挑選出來,在載入其他bean
前,優先載入BeanPostProcessor
的實現類。
還有另外一種方式就是使用ConfigurableBeanFactory
介面的addBeanPostProcessor
方法手動新增,ApplicationContext
物件中組合了一個ConfigurableBeanFactory
的實現類物件。但是這種方式新增BeanPostProcessor
有一些缺點。首先,我們一建立Spring
容器,在配置檔案中配置的單例bean
就會被載入,此時addBeanPostProcessor
方法還沒有執行,那我們手動新增的BeanPostProcessor
也就無法作用於這些bean
了,所以手動新增的BeanPostProcessor
只能作用於那些延遲載入的bean
,或者非單例bean
。
還有一個就是,使用addBeanPostProcessor方式新增的BeanPostProcessor,Ordered介面的作用將失效,而是以註冊的順序執行。我們前面提過,Ordered
介面用來指定多個BeanPostProcessor
實現的方法的執行順序。這是Spring
官方文件中提到的:
While the recommended approach for
BeanPostProcessor
registration is throughApplicationContext
auto-detection (as described above), it is also possible to register them programmatically against aConfigurableBeanFactory
using theaddBeanPostProcessor
method. This can be useful when needing to evaluate conditional logic before registration, or even for copying bean post processors across contexts in a hierarchy. Note however thatBeanPostProcessor
s added programmatically do not respect theOrdered
interface. Here it is the order of registration that dictates the order of execution. Note also thatBeanPostProcessor
s registered programmatically are always processed before those registered through auto-detection, regardless of any explicit ordering.
(四)使用@Bean配置BeanPostProcessor的限制
如果我們使用Java
類的方式配置Spring
,並使用@Bean
宣告一個工廠方法返回bean
例項,那麼返回值的型別必須是BeanPostProcessor
型別,或者等級低於BeanPostProcessor
的型別。這裡不好口頭描述,直接看程式碼吧。以下是一個BeanPostProcessor
的實現類,它實現了多個介面:
/** * 此BeanPostProcessor的實現類,還實現了Ordered介面 */ public class PostBean implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("before -- " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("after -- " + beanName); return bean; } @Override public int getOrder() { return 0; } }
我們在配置類中,宣告PostBean
可以有以下幾種方式:
@Configuration public class BeanConfig { // 方式1:PostBean @Bean public PostBean postBean() { return new PostBean(); } // 方式2:返回值為BeanPostProcessor @Bean public BeanPostProcessor postBean() { return new PostBean(); } // 方式3:返回值為Ordered @Bean public Ordered postBean() { return new PostBean(); } }
以上三種方式都可以讓Spring
容器建立PostBean
例項物件,因為PostBean
實現了BeanPostProcessor
和Ordered
介面,所以它的物件也是這兩種型別的物件。但是需要注意,上面三種方式中,只有第一種和第二種方式,會讓Spring
容器將PostBean
當作BeanPostProcessor
處理;而第三種方式,則會被當作一個普通Bean
處理,實現BeanPostProcessor
的兩個方法都不會被呼叫。因為在PostBean
的繼承體系中,Ordered
和BeanPostProcessor
是同級別的,Spring
無法識別出這個Ordered
物件,也是一個BeanPostProcessor
物件;但是使用PostBean
卻可以,因為PostBean
型別就是BeanPostProcessor
的子型別。所以,在使用@Bean宣告工廠方法返回BeanPostProcessor實現類物件時,返回值必須是BeanPostProcessor型別,或者更低階的型別。Spring
官方文件中,這一部分的內容如下:
Note that when declaring a
BeanPostProcessor
using an@Bean
factory method on a configuration class, the return type of the factory method should be the implementation class itself or at least theorg.springframework.beans.factory.config.BeanPostProcessor
interface, clearly indicating the post-processor nature of that bean. Otherwise, theApplicationContext
won’t be able to autodetect it by type before fully creating it. Since aBeanPostProcessor
needs to be instantiated early in order to apply to the initialization of other beans in the context, this early type detection is critical.
三. 總結
以上就對BeanPostProcessor
的功能、使用以及需要注意的問題做了一個大致的介紹。需要注意的是,上面所提到的問題,可能根據不同的情況,會有不同的結果,因為文件中的資料只是簡單地提了幾句,並不詳細,上面的內容大部分都是我基於官方文件的描述,以及自己的測試得出,所以可能並不準確。還需要自己在實踐中去嘗試,或者閱讀原始碼,才能徹底瞭解BeanPostProcessor
的執行機制。
以上描述若存在錯誤或不足,希望能夠提出來,因為這一部分內容,我也不太瞭解,所以希望有人幫忙指正。