【Spring学习23】容器扩展点:后置处理器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

但是有个超出预料的,就是每一个方法调用都多出了一个:“发生时间:2017-03-31 15:22:07”。这怎么回事呢?
后面有空再研究。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值