Spring BeanPostProcessor(后置处理器)的用法
为了弄清楚spring框架,我们需要分别弄清楚相关核心接口的作用,本文来介绍下beanpostprocessor接口
beanpostprocessor
该接口我们也叫后置处理器,作用是在bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是bean实例化完毕后及依赖注入完成后触发的。接口的源码如下
public interface beanpostprocessor { object postprocessbeforeinitialization(object bean, string beanname) throws beansexception; object postprocessafterinitialization(object bean, string beanname) throws beansexception; }
方法 | 说明 |
---|---|
postprocessbeforeinitialization | 实例化、依赖注入完毕, 在调用显示的初始化之前完成一些定制的初始化任务 |
postprocessafterinitialization | 实例化、依赖注入、初始化完毕时执行 |
一、自定义后置处理器演示
1.自定义处理器
package com.dpb.processor; import org.springframework.beans.beansexception; import org.springframework.beans.factory.config.beanpostprocessor; /** * 自定义beanpostprocessor实现类 * beanpostprocessor接口的作用是: * 我们可以通过该接口中的方法在bean实例化、配置以及其他初始化方法前后添加一些我们自己的逻辑 * @author dengp * */ public class mybeanpostprocessor implements beanpostprocessor{ /** * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { system.out.println("初始化 before--实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } /** * 实例化、依赖注入、初始化完毕时执行 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessafterinitialization(object bean, string beanname) throws beansexception { system.out.println("初始化 after...实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } }
注意:接口中两个方法不能返回null,如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象,因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中
2.pojo类
public class user { private int id; private string name; private string beanname; public user(){ system.out.println("user 被实例化"); } public int getid() { return id; } public void setid(int id) { this.id = id; } public string getname() { return name; } public void setname(string name) { system.out.println("设置:"+name); this.name = name; } public string getbeanname() { return beanname; } public void setbeanname(string beanname) { this.beanname = beanname; } /** * 自定义的初始化方法 */ public void start(){ system.out.println("user 中自定义的初始化方法"); } }
3.配置文件注册
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.dpb.pojo.user" id="user" init-method="start"> <property name="name" value="波波烤鸭" /> </bean> <!-- 注册处理器 --> <bean class="com.dpb.processor.mybeanpostprocessor"></bean> </beans>
4.测试
@test public void test() { applicationcontext ac = new classpathxmlapplicationcontext("applicationcontext.xml"); user user = ac.getbean(user.class); system.out.println(user); }
输出结果
user 被实例化
设置:波波烤鸭
初始化 before--实例化的bean对象:com.dpb.pojo.user@65e2dbf3 user
user 中自定义的初始化方法
初始化 after...实例化的bean对象:com.dpb.pojo.user@65e2dbf3 user
com.dpb.pojo.user@65e2dbf3
通过输出语句我们也能看到postprocessbeforeinitialization方法的输出语句是在bean实例化及属性注入后执行的,且在自定义的初始化方法之前执行(通过init-method指定)。而postprocessafterinitialization方法是在自定义初始化方法执行之后执行的。
注意!!!
beanfactory和applicationcontext两个容器对待bean的后置处理器稍微有些不同。applicationcontext容器会自动检测spring配置文件中那些bean所对应的java类实现了beanpostprocessor接口,并自动把它们注册为后置处理器。在创建bean过程中调用它们,所以部署一个后置处理器跟普通的bean没有什么太大区别。
beanfactory容器注册bean后置处理器时必须通过代码显示的注册,在ioc容器继承体系中的configurablebeanfactory接口中定义了注册方法
/** * add a new beanpostprocessor that will get applied to beans created * by this factory. to be invoked during factory configuration. * <p>note: post-processors submitted here will be applied in the order of * registration; any ordering semantics expressed through implementing the * {@link org.springframework.core.ordered} interface will be ignored. note * that autodetected post-processors (e.g. as beans in an applicationcontext) * will always be applied after programmatically registered ones. * @param beanpostprocessor the post-processor to register */ void addbeanpostprocessor(beanpostprocessor beanpostprocessor);
测试代码如下
@test public void test2() { //applicationcontext ac = new classpathxmlapplicationcontext("applicationcontext.xml"); xmlbeanfactory bf = new xmlbeanfactory(new classpathresource("applicationcontext.xml")); // 显示添加后置处理器 bf.addbeanpostprocessor(bf.getbean(mybeanpostprocessor.class)); user user = bf.getbean(user.class); system.out.println(user); }
二、多个后置处理器
我们可以在spring配置文件中添加多个beanpostprocessor(后置处理器)接口实现类,在默认情况下spring容器会根据后置处理器的定义顺序来依次调用。
public class mybeanpostprocessor implements beanpostprocessor{ /** * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { system.out.println("a before--实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } /** * 实例化、依赖注入、初始化完毕时执行 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessafterinitialization(object bean, string beanname) throws beansexception { system.out.println("a after...实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } }
public class mybeanpostprocessor2 implements beanpostprocessor{ /** * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { system.out.println("b before--实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } /** * 实例化、依赖注入、初始化完毕时执行 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessafterinitialization(object bean, string beanname) throws beansexception { system.out.println("b after...实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } }
配置文件注册
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.dpb.pojo.user" id="user" init-method="start"> <property name="name" value="波波烤鸭" /> </bean> <!-- 注册处理器 --> <bean class="com.dpb.processor.mybeanpostprocessor"/> <bean class="com.dpb.processor.mybeanpostprocessor2"/> </beans>
测试结果
user 被实例化
设置:波波烤鸭
a before--实例化的bean对象:com.dpb.pojo.user@7fac631b user
b before--实例化的bean对象:com.dpb.pojo.user@7fac631b user
user 中自定义的初始化方法
a after...实例化的bean对象:com.dpb.pojo.user@7fac631b user
b after...实例化的bean对象:com.dpb.pojo.user@7fac631b user
com.dpb.pojo.user@7fac631b
三、显示指定顺序
在spring机制中可以指定后置处理器调用顺序,通过让beanpostprocessor接口实现类实现ordered接口getorder方法,该方法返回一整数,默认值为 0,优先级最高,值越大优先级越低
public class mybeanpostprocessor implements beanpostprocessor,ordered{ /** * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { system.out.println("a before--实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } /** * 实例化、依赖注入、初始化完毕时执行 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessafterinitialization(object bean, string beanname) throws beansexception { system.out.println("a after...实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } @override public int getorder() { // todo auto-generated method stub return 10; } }
public class mybeanpostprocessor2 implements beanpostprocessor,ordered{ /** * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { system.out.println("b before--实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } /** * 实例化、依赖注入、初始化完毕时执行 * 注意:方法返回值不能为null * 如果返回null那么在后续初始化方法将报空指针异常或者通过getbean()方法获取不到bena实例对象 * 因为后置处理器从spring ioc容器中取出bean实例对象没有再次放回ioc容器中 */ @override public object postprocessafterinitialization(object bean, string beanname) throws beansexception { system.out.println("b after...实例化的bean对象:"+bean+"\t"+beanname); // 可以根据beanname不同执行不同的处理操作 return bean; } @override public int getorder() { // todo auto-generated method stub return 2; } }
测试输出结果
user 被实例化
设置:波波烤鸭
b before--实例化的bean对象:com.dpb.pojo.user@7fac631b user
a before--实例化的bean对象:com.dpb.pojo.user@7fac631b user
user 中自定义的初始化方法
b after...实例化的bean对象:com.dpb.pojo.user@7fac631b user
a after...实例化的bean对象:com.dpb.pojo.user@7fac631b user
com.dpb.pojo.user@7fac631b
数值越大的优先级越低,所以a的输出就在后面了。
对beanpostprocessor接口的理解
今天想起来写一篇,是因为自己纠正了对beanpostprocessor接口的理解误区,写文章往往都是源于这种豁然开朗的灵感。不过今天又是孤陋寡闻的一天呢,一个知识点理解错了这么长时间居然都不自知。
bean的生命周期应该都很清楚,先贴这张图
这张bean生命周期顺序图里大部分环节都是比较好理解的,比如setbeanname和setbeanfactory包括setapplicationcontext,都是实现了对应的接口,就可以在实例化这个bean的时候为这个bean设置beanname,或者注入beanfactory和applicationcontext,可以用于获取ioc容器中的其他bean等。但是实现了beanpostprocessor接口不能按这个逻辑去理解,
先看一下实现beanpostprocessor接口后的重写哪两个方法:
之前我一直是按这个理解逻辑去理解实现了beanpostprocessor接口,理解误区是:mybeanpost这个类实现了beanpostprocessor接口,实例化的mybeanpost的时候就会去调用该类里重写的postprocessbeforeinitialization()和postprocessafterinitialization()方法,这两个方法里拿到的beanname就是@component里定义的"mybeanpost",object类型的bean就是mybeanpost对象,然后实例化mybeanpost对象前后去在这两个方法里做点什么。
之前一直是这么理解的,其实一直是有疑惑的,因为按这么理解,postprocessbeforeinitialization()方法能做的在自定义的@postconstruct方法里也能做,那这两个就区分不开了。虽然有疑惑但自己也没有去试过,直到今天项目开发的时候真的想用postprocessafterinitialization()方法去在初始化完一个bean的时候注入点东西的时候,一试傻眼了。
直接贴上图那种写法时的启动日志:
如果按我之前那么理解,这里应该只打印出"mybeanpost执行了postprocessbeforeinitialization"和"mybeanpost执行了postprocessafterinitialization"才对啊,居然打印出了这么多,而且我全局搜了一下,偏偏没有beanname是"mybeanpost"的日志记录。这个时候我才知道之前我一直理解错了,于是重视起来开始找原因。
spring的源码一顿翻之后找到了spring初始化bean的一段代码:
这里的invokeinitmethods()就是反射调用我们自定义的初始化方法,即顺序图中的第八步,可以清楚的看到applybeanpostprocessorsbeforeinitialization()方法在前,applybeanpostprocessorsafterinitialization()方法在后,这似乎也和顺序图中执行postprocessbeforeinitialization()在执行自定义初始化方法前,执行postprocessafterinitialization()在后对应上了,继续点进去看,
先看applybeanpostprocessorsbeforeinitialization()方法
需要注意这里existingbean参数是正在实例化的bean,这里的getbeanpostprocessors()方法是去拿所有实现了beanpostprocessor接口的实现类的bean,然后再调用beanpostprocessor接口实现类的postprocessbeforeinitialization()方法。
看到这里就推翻了我之前的理解了,原来一个类实现了beanpostprocessor接口,那重写的两个方法不是实例化该类的时候调用的,而是容器实例化其他bean的时候调用的,容器会找出当前容器中所有实现了beanpostprocessor接口实现类对象,然后一个遍历一个一个调用,这也是为什么上图打印出来的日志会有这么多beanname的日志记录。也就是说如果容器需要实例化n个bean,同时容器中已有m个beanpostprocessor接口实现类对象,那beanpostprocessor接口的那两个方法就会被调用n*m次,虽然是在不同的实现类中调用的。
applybeanpostprocessorsafterinitialization()同理:
一样的,总结一下,对于beanpostprocessor接口的理解的理解应该是这样:
beanpostprocessor接口有两个方法,分别为bean容器中每个对象被实例化前和实例化后调用,即交给bean容器管理的所有对象,比如打了@component,@restcontroller等注解的类,程序启动时就会去实例化并放到bean容器中的这些类。每次实例化一个bean都会调用beanpostprocessor接口重写方法,所有那两个方法是被多次调用的。应该在那两个方法中很据beanname拿到自己想处理的bean实例,再去做对应处理。
文章开头也提到,日志打印了这么多,偏偏没有beanname是"mybeanpost"的日志记录,也就是说,beanpostprocessor接口的实现类明明也打了@component注解,可是为啥在自己的重写方法中打印不出来beanname是自己的日志记录呢?这是正常而且必然的。因为虽然mybeanpost也打了@component,程序启动时也会去实例化这个bean,但是实例化它的时候,getbeanpostprocessors()方法是拿不到mybeanpost自己这个bean的。还没实例化完呢怎么放进bean容器中,没放进去当然拿不到了,自然也不会去执行这个bean里重写的方法了。
不过如果另外写一个beanpostprocessor接口的实现类就不一定了,如果实例化mybeanpost的时候另一个beanpostprocessor实现类已经被实例化好了放进bean容器中了,getbeanpostprocessors()就能拿到,然后在另一个beanpostprocessor实现类里重写的方法里打印出beanname为"mybeanpost"的日志记录。反正,beanpostprocessor实现类不能在自己重写的方法中不能拿到自己的bean实例。
所以beanpostprocessor接口的正确用法应该是写一个mybeanpostprocessor实现类,意思是自定义的bean处理类,然后在这个自定义的bean处理类中根据beanname去筛选拿到bean容器中的其他bean,做一些实例化这些bean之前之后的逻辑。例如这样:
要注意重写的方法默认是返回null的,这里要返回处理后的bean,如果返回null就会是处理之前的bean,这个逻辑在applybeanpostprocessorsbeforeinitialization()方法和applybeanpostprocessorsafterinitialization()方法里,
之前贴的图里有,再贴一遍:
current即自定义处理之后的,如果是null,还是返回result。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
Spring中@Import的各种用法以及ImportAware接口
-
详解Spring Cloud负载均衡重要组件Ribbon中重要类的用法
-
首款支持5G的iPad Pro预计9月发布:5nm A14处理器、后置多摄
-
详解Spring Data JPA系列之投影(Projection)的用法
-
使用Spring的BeanPostProcessor优雅的实现工厂模式
-
HandlerMethodArgumentResolver(三):基于消息转换器的参数处理器【享学Spring MVC】
-
Spring中的InstantiationAwareBeanPostProcessor和BeanPostProcessor
-
spring中的BeanPostProcessor
-
Spring 中的 BeanPostProcessor接口
-
spring中的BeanPostProcessor