談談Spring中的BeanPostProcessor接口

1、前言

  這幾天正在複習Spring的相關內容,在瞭解bean的生命週期的時候,發現其中涉及到一個特殊的接口——BeanPostProcessor接口。因爲網上沒有找到比較好的博客,全部最後花了好幾個小時,經過Spring的官方文檔對它作了一個大體的瞭解,下面就來簡單介紹一下這個接口。html


2、正文

2.1 BeanPostProcessor的功能

  有時候,咱們但願Spring容器在建立bean的過程當中,可以使用咱們本身定義的邏輯,對建立的bean作一些處理,或者執行一些業務。而實現方式有多種,好比自定義bean的初始化話方法等,而BeanPostProcessor接口也是用來實現相似的功能的。java

  若是咱們但願容器中建立的每個單例bean,在建立的過程當中能夠執行一些自定義的邏輯,那麼咱們就能夠編寫一個類,並讓他實現BeanPostProcessor接口,而後將這個類註冊到一個容器中。容器在建立bean的過程當中,會優先建立實現了BeanPostProcessor接口的bean,而後,在建立其餘bean的時候,會將建立的每個bean做爲參數,調用BeanPostProcessor的方法。而BeanPostProcessor接口的方法,便是由咱們本身實現的。下面就來具體介紹一下BeanPostProcessor的使用spring


2.2 BeanPostProcessor的使用

  咱們先看一看BeanPostProcessor接口的代碼:app

public interface BeanPostProcessor {
	// 注意這個方法名稱關鍵的是before這個單詞
	Object postProcessBeforeInitialization(Object bean, String beanName) 
        throws BeansException;

    // 注意這個方法名稱關鍵的是after這個單詞
	Object postProcessAfterInitialization(Object bean, String beanName) 
        throws BeansException;
}

  能夠看到,BeanPostProcessor接口只有兩個抽象方法,由實現這個接口的類去實現(後面簡稱這兩個方法爲beforeafter),這兩個方法有着相同的參數:less

  • bean:容器正在建立的那個bean的引用;
  • beanName:容器正在建立的那個bean的名稱;

  那這兩個方法什麼時候執行呢?這就涉及到Spring中,bean的生命週期了。下面引用《Spring實戰》中的一張圖,這張圖表現了bean的生命週期,而Spring容器建立bean的具體過程,請參考這篇博客——簡單談談Spring的IoCide

  上圖中標紅的兩個地方就是BeanPostProcessor中兩個方法的執行時機。Spring容器在建立bean時,若是容器中包含了BeanPostProcessor的實現類對象,那麼就會執行這個類的這兩個方法,並將當前正在建立的bean的引用以及名稱做爲參數傳遞進方法中。這也就是說,BeanPostProcessor的做用域是當前容器中的全部bean(不包括一些特殊的bean,這個後面說)。post

  值得注意的是,咱們能夠在一個容器中註冊多個不一樣的BeanPostProcessor的實現類對象,而bean在建立的過程當中,將會輪流執行這些對象實現的beforeafter方法。那執行順序如何肯定呢?Spring提供了一個接口Ordered,咱們可讓BeanPostProcessor的實現類實現這個Ordered接口,並實現接口的getOrder方法。這個方法的返回值是一個int類型,Spring容器會經過這個方法的返回值,對容器中的多個BeanPostProcessor對象進行從小到大排序,而後在建立bean時依次執行它們的方法。也就是說,getOrder方法返回值越小的BeanPostProcessor對象,它的方法將越先被執行。測試


2.3 一個簡單的demo

  下面就來寫一個簡單的demo,來看看BeanPostProcessor的效果。首先定義兩個普通的bean,就叫UserCar吧:this

public class User {

    private String name;
    private int age;
	
    // ... 省略getter和setter...
}

public class Car {
    private int speed;
    private double price;

    // ... 省略getter和setter...
}

  在定義一個BeanPostProcessor的實現類,重寫接口的方法:lua

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配置文件了。配置以下,在這個配置類中,聲明瞭UserCar以及PostBean這三個bean的工廠方法,前兩個是普通bean,而PostBean是實現BeanPostProcessorbean

@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的上下文對象,也就是SpringIoC容器。這個容器將去加載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

  能夠看到,BeanPostProcessorbefore方法和after方法都被調用了四次,最後兩次調用時,傳入的參數正是咱們本身定義的Bean——UserCar。那爲何調用了四次呢,明明咱們只定義了兩個普通bean。咱們看上面的輸出發現,前兩次調用,傳入的beanSpring內部的組件。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, neither BeanPostProcessor s nor the beans they reference directly are eligible for auto-proxying, and thus do not have aspects woven into them.

  上面這段話的意思大體是說,SpringAOP代理就是做爲BeanPostProcessor實現的,因此咱們沒法對BeanPostProcessor的實現類使用AOP織入通知,也沒法對BeanPostProcessor的實現類依賴的bean使用AOP織入通知SpringAOP實現我暫時尚未研究過,因此上面的說AOP做爲BeanPostProcessor實現的意思我不是特別明白,可是咱們如今只須要關注BeanPostProcessor以及它依賴的bean都沒法使用AOP這一點。爲了驗證上面的說法,我稍微修改一下2.3中的例子,來測試一波。

  首先,咱們修改2.3中用到的PostBeanUser這兩個類,讓PostBean依賴User這個類,同時爲了輸出更加地簡單,咱們將beforeafter方法中的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的切面,在切面中將PostBeantestAOP方法做爲切點,代碼以下:

@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 through ApplicationContext auto-detection (as described above), it is also possible to register them programmatically against a ConfigurableBeanFactory using the addBeanPostProcessor 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 that BeanPostProcessor s added programmatically do not respect the Ordered interface. Here it is the order of registration that dictates the order of execution. Note also that BeanPostProcessor 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實現了BeanPostProcessorOrdered接口,因此它也是這兩種類型的對象。可是須要注意,上面三種方式中,只有第一種和第二種方式,會讓Spring容器將PostBean看成BeanPostProcessor處理;而第三種方式,則會被看成一個普通Bean處理,實現BeanPostProcessor的兩個方法都不會被調用。由於在PostBean的繼承體系中,OrderedBeanPostProcessor是同級別的,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 the org.springframework.beans.factory.config.BeanPostProcessor interface, clearly indicating the post-processor nature of that bean. Otherwise, the ApplicationContext won’t be able to autodetect it by type before fully creating it. Since a BeanPostProcessor needs to be instantiated early in order to apply to the initialization of other beans in the context, this early type detection is critical.


3、總結

  以上就對BeanPostProcessor的功能、使用以及須要注意的問題作了一個大體的介紹。須要注意的是,上面所提到的問題,可能根據不一樣的狀況,會有不一樣的結果,由於文檔中的資料只是簡單地提了幾句,並不詳細,上面的內容大部分都是我基於官方文檔的描述,以及本身的測試得出,因此可能並不許確。還須要本身在實踐中去嘗試,或者閱讀源碼,才能完全瞭解BeanPostProcessor的執行機制。

  以上描述若存在錯誤或不足,但願可以提出來,由於這一部份內容,我也不太瞭解,因此但願有人幫忙指正


4、參考

相關文章
相關標籤/搜索