談談Spring中的BeanPostProcessor接口(轉)
原文:談談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
BeanPostProcessoritself, neitherBeanPostProcessors 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
BeanPostProcessorregistration is throughApplicationContextauto-detection (as described above), it is also possible to register them programmatically against aConfigurableBeanFactoryusing theaddBeanPostProcessormethod. 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 thatBeanPostProcessors added programmatically do not respect theOrderedinterface. Here it is the order of registration that dictates the order of execution. Note also thatBeanPostProcessors 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
BeanPostProcessorusing an@Beanfactory 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.BeanPostProcessorinterface, clearly indicating the post-processor nature of that bean. Otherwise, theApplicationContextwon’t be able to autodetect it by type before fully creating it. Since aBeanPostProcessorneeds 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的執行機制。
??以上描述若存在錯誤或不足,希望能夠提出來,因為這一部分內容,我也不太了解,所以希望有人幫忙指正。

浙公網安備 33010602011771號