談談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介面只有兩個抽象方法,由實現這個介面的類去實現(後面簡稱這兩個方法為beforeafter),這兩個方法有著相同的參數:

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

  那這兩個方法何時執行呢?這就涉及到Spring中,bean的生命周期了。下面引用《Spring實戰》中的一張圖,這張圖表現了bean的生命周期,而Spring容器創建bean的具體過程,請參考這篇部落格——簡單談談Spring的IoC

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

  值得注意的是,我們可以在一個容器中註冊多個不同的BeanPostProcessor的實現類對象,而bean在創建的過程中,將會輪流執行這些對象實現的beforeafter方法。那執行順序如何確定呢?Spring提供了一個介面Ordered,我們可以讓BeanPostProcessor的實現類實現這個Ordered介面,並實現介面的getOrder方法。這個方法的返回值是一個int類型,Spring容器會通過這個方法的返回值,對容器中的多個BeanPostProcessor對象進行從小到大排序,然後在創建bean時依次執行它們的方法。也就是說,getOrder方法返回值越小的BeanPostProcessor對象,它的方法將越先被執行。

2.3 一個簡單的demo

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

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配置文件了。配置如下,在這個配置類中,聲明了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.

三、總結

  以上就對BeanPostProcessor的功能、使用以及需要注意的問題做了一個大致的介紹。需要注意的是,上面所提到的問題,可能根據不同的情況,會有不同的結果,因為文檔中的資料只是簡單地提了幾句,並不詳細,上面的內容大部分都是我基於官方文檔的描述,以及自己的測試得出,所以可能並不準確。還需要自己在實踐中去嘗試,或者閱讀源碼,才能徹底了解BeanPostProcessor的執行機制。

  以上描述若存在錯誤或不足,希望能夠提出來,因為這一部分內容,我也不太了解,所以希望有人幫忙指正

四、參考

Tags: