1. 程式人生 > >Spring容器擴充套件點:後置處理器BeanPostProcessor

Spring容器擴充套件點:後置處理器BeanPostProcessor

先回顧bean生命週期的這張圖,看看BeanPostProcessor呼叫位置 
這裡寫圖片描述

通過上圖看到BeanPostProcessor(Bean後置處理器)兩個方法在bean生命週期的位置,即:在Spring容器完成Bean例項化和屬性設定後,並且在bean呼叫初始化方法之前或之後。因此BeanPostProcessor(Bean後置處理器)常用在:對bean內部的值進行修改;實現Bean的動態代理等。

可以定義一個或者多個BeanPostProcessor介面的實現,然後註冊到容器中。那麼該容器裡管控的所有Bean在呼叫初始化方法之前或之後,都會呼叫BeanPostProcessor介面中對應的方法。 
InstantiationAwareBeanPostProcessor是BeanPostProcessor的子介面。從最上面的生命週期圖,我們知道它在Bean生命週期的另外三個時期提供擴充套件的回撥介面。其使用方法與BeanPostProcessor介面類似,只時回撥時機不同。

BeanPostProcessor介面有兩個方法:

Object postProcessBeforeInitialization(Object bean,String BeanName)throws BeansException;
Object postProcessAfterInitialization(Object bean,String BeanName)throws BeansException;

容器呼叫介面定義的方法時會將該受管Bean的例項和名字通過引數傳入方法,經過處理後通過方法的返回值返回給容器。注意,不能返回null,如果返回的是null那麼我們通過getBean方法將得不到目標。

BeanPostProcessor不允許標記為延遲載入。因為如果這樣做,Spring容器將不會註冊它們,自定義邏輯也就無法得到應用。假如你在<beans />元素的定義中使用了'default-lazy-init'屬性,那就必須將每個BeanPostProcessor顯示標記為'lazy-init="false"'

如果定義了多個BeanPostProcessor,可以在xml配置中通過order屬性來指定執行的順序。

簡單例子

類程式碼:(在這個例子中,可以不需要繼承介面。但為了保持和原來的程式一致,就沒有刪implements PlayerActionInterface

了):

package twm.spring.LifecycleTest;
public class footballPlayer implements PlayerActionInterface{{

    String name;//球員名字
    String team;//所在球隊

    //getter and setter......

    public void shoot() {
        System.out.println(this.getName()+"射門");
    }
    public void pass() {
        System.out.println(this.getName()+"邊路傳中");
    }
}

註冊:

<bean id="cluo" class="twm.spring.LifecycleTest.footballPlayer">
    <property name="name" value="C.羅納爾多"></property>
</bean>

這是原來的程式。呼叫:

public static void main(String[] args) throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    footballPlayer smone = ctx.getBean("cluo",footballPlayer.class);
    smone.shoot();
    smone.pass();
}

輸出:

C.羅納爾多射門 
C.羅納爾多邊路傳中

現在我們加入一個beanPostProcessor後處理器(beanPostProcessorImpl.java),修改球員名稱:

package twm.spring.LifecycleTest;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class beanPostProcessorImpl implements BeanPostProcessor{
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if(bean instanceof footballPlayer){
            ((footballPlayer) bean).setName("Messi");
        }
        return bean;
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }
}

配置檔案beans.xml加上:

<bean class="twm.spring.LifecycleTest.beanPostProcessorImpl" />
  • 其它不變,這時再執行,輸出:

Messi射門 
Messi邊路傳中

代理使用例子

在剛才的例子基礎上,有新的需求:教練團隊需要在每一次呼叫pass(),shoot()方法時,記錄呼叫時間,用來進行戰術分析。 
於是重寫beanPostProcessor後處理器(beanPostProcessorImpl.java) 
程式碼如下:

public class beanPostProcessorImpl implements BeanPostProcessor{

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {

        /* 後處理器beanPostProcessor會對容器中所有的bean起作用,因此我們要限定一下範圍。
         * 這個例子中,我們只處理PlayerActionInterface物件*/
        if(!(bean instanceof PlayerActionInterface)){
            return bean;
        }

        final Object finalBean=bean;
        Map map = new ConcurrentHashMap(100);  

        if(map.get(beanName)!=null){
            return map.get(beanName);
        }

        Class[] classes=bean.getClass().getInterfaces();
        if(classes.length<1){
            //沒有介面的,無法進行代理
            return bean;
        }

        Object proxyObj = Proxy.newProxyInstance(this.getClass().getClassLoader(),
                classes,
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        System.out.println("method:" + method.getName());
                        Object result = method.invoke(finalBean, args);
                        System.out.println("發生時間:"
                                + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
                                        .format(new Date()));
                        return result;
                    }
                });
        map.put(beanName, proxyObj);
        return proxyObj;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }
}

業務呼叫程式碼要將以前的類宣告改成介面宣告PlayerActionInterface, 
因此調整為:

public static void main(String[] args) throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    PlayerActionInterface smone = ctx.getBean("cluo",PlayerActionInterface.class);
    smone.shoot();
    smone.pass();
}

這時再執行程式碼輸出:

method:shoot 
C.羅納爾多射門 
發生時間:2017-03-31 14:09:02 
method:pass 
C.羅納爾多邊路傳中 
發生時間:2017-03-31 14:09:02

補充: 
當然也可以用beanPostProcessor實現AOP代理。

後置處理器BeanPostProcessor對上一篇提到的FactoryBean產生的bean也是有效的(雙重代理)。 
如果我們把剛才定義的beanPostProcessorImpl類註冊到上一篇的例子中去,就會輸出:

getObject 
ctx.getBean(“playerfacory”):com.sun.proxy.$Proxy1 
ctx.getBean(“&playerfacory”):twm.spring.LifecycleTest.PlayerFactory 
----------------------- 
method:shoot 
method:shoot 
觀察進攻及防守隊員跑位 
method:shoot 
C.羅納爾多射門 
發生時間:2017-03-31 15:22:07 
無球跑動 
發生時間:2017-03-31 15:22:07 
method:pass 
method:pass 
觀察進攻及防守隊員跑位 
method:pass 
C.羅納爾多邊路傳中 
發生時間:2017-03-31 15:22:07 
無球跑動 
發生時間:2017-03-31 15:22:07