談談Spring中的BeanPostProcessor介面(轉)

奋斗终生發表於2024-09-06

原文:談談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的執行機制。

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

相關文章