Spring IOC官方文档学习笔记(八)之容器扩展点,通过BeanPost
Spring IOC官方文档学习笔记(八)之容器扩展点,通过BeanPost
1.通过BeanPostProcessor来自定义bean
(1) BeanPostProcessor用于在容器完成了对bean的实例化,配置及初始化后来实现一些自定义逻辑,它是用于操纵由容器创建的每个bean实例的,即在容器实例化了一个bean后以及该bean的初始化回调(如InitializingBean.afterPropertiesSet()等)被执行之前,会将这个bean交由BeanPostProcessor来进行处理。通过BeanPostProcessor,我们可以对bean实例进行任何操作,包括忽略掉后续的初始化回调等,BeanPostProcessor通常用来检查回调接口,或用来生成某个bean的代理对象,因此Spring AOP的实现基础就是BeanPostProcessor,如下是简单使用BeanPostProcessor的一个例子
//让ExampleA实现3个初始化回调
public class ExampleA implements InitializingBean {
private String name;
public ExampleA() {
System.out.println("ExampleA的构造方法被调用");
System.out.println("----------------------------------------");
}
//这个方法只用于IOC的属性注入
public void setName(String name) {
System.out.println("IOC对ExampleA的name属性进行注入,值为:" + name);
System.out.println("----------------------------------------");
this.name = name;
}
//这个方法用于我们自己手动注入
public void setNameInOtherWay(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "ExampleA{" +
"name='" + name + '\'' +
'}';
}
@PostConstruct
public void postConstruct() {
System.out.println("正在执行初始化回调PostConstruct,此时的ExampleA为:" + this);
this.setNameInOtherWay("zzz2");
System.out.println("执行完毕,此时的ExampleA为:" + this);
System.out.println("----------------------------------------");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("正在执行初始化回调InitializingBean.afterPropertiesSet,此时的ExampleA为:" + this);
this.setNameInOtherWay("zzz3");
System.out.println("执行完毕,此时的ExampleA为:" + this);
System.out.println("----------------------------------------");
}
public void init() {
System.out.println("正在执行初始化回调init-method,此时的ExampleA为:" + this);
this.setNameInOtherWay("zzz4");
System.out.println("执行完毕,此时的ExampleA为:" + this);
System.out.println("----------------------------------------");
}
}
//实现BeanPostProcessor,自定义后置处理器来操纵bean实例(注意:需要把我们的自定义处理器注入到容器中),它主要提供了2个方法
public class Processor implements BeanPostProcessor {
/**
* 该方法作用于bean实例创建配置好后,初始化回调执行前,来自定义一些逻辑
* @param bean 实例化并配置好后的bean
* @param beanName bean的名称
* @return 自定义操作完成后的bean
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("正在执行postProcessBeforeInitialization,此时的ExampleA为:" + bean);
((ExampleA) bean).setNameInOtherWay("zzz1");
System.out.println("执行完毕,此时的ExampleA为:" + bean);
System.out.println("----------------------------------------");
return bean;
}
/**
* 该方法作用于初始化回调执行后
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("正在执行postProcessAfterInitialization,此时的ExampleA为:" + bean);
((ExampleA) bean).setNameInOtherWay("zzz5");
System.out.println("执行完毕,此时的ExampleA为:" + bean);
System.out.println("----------------------------------------");
return bean;
}
}
<!-- xml配置文件 -->
<beans ....>
<!-- 开启注解扫描,否则@PostConstruct注解不生效 -->
<context:annotation-config></context:annotation-config>
<bean id="exampleA" class="cn.example.spring.boke.ExampleA" init-method="init">
<property name="name" value="zzz"></property>
</bean>
<!-- 注意仅仅实现接口是不行的,我们必须把自定义后置处理器注册成bean,它才会生效 -->
<bean id="processor" class="cn.example.spring.boke.Processor"></bean>
</beans>
//启动容器后,打印结果为
ExampleA的构造方法被调用
----------------------------------------
IOC对ExampleA的name属性进行注入,值为:zzz
----------------------------------------
正在执行postProcessBeforeInitialization,此时的ExampleA为:ExampleA{name='zzz'}
执行完毕,此时的ExampleA为:ExampleA{name='zzz1'}
----------------------------------------
正在执行初始化回调PostConstruct,此时的ExampleA为:ExampleA{name='zzz1'}
执行完毕,此时的ExampleA为:ExampleA{name='zzz2'}
----------------------------------------
正在执行初始化回调InitializingBean.afterPropertiesSet,此时的ExampleA为:ExampleA{name='zzz2'}
执行完毕,此时的ExampleA为:ExampleA{name='zzz3'}
----------------------------------------
正在执行初始化回调init-method,此时的ExampleA为:ExampleA{name='zzz3'}
执行完毕,此时的ExampleA为:ExampleA{name='zzz4'}
----------------------------------------
正在执行postProcessAfterInitialization,此时的ExampleA为:ExampleA{name='zzz4'}
执行完毕,此时的ExampleA为:ExampleA{name='zzz5'}
----------------------------------------
综上可见,添加了BeanPostProcessor后,bean的初始化流程为:执行bean的构造函数 -> IOC进行属性注入 -> BeanPostProcessor.postProcessBeforeInitialization -> 三大初始化回调 -> BeanPostProcessor.postProcessAfterInitialization
(2) 我们可以向容器中注入多个自定义BeanPostProcessor,并通过实现Ordered接口来控制这些BeanPostProcessor的执行顺序,如下所示
public class ExampleA { }
//让该自定义后置处理器实现Ordered接口,指定它在所有自定义后置处理器中的执行顺序
public class Processor0 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Order值为0的postProcessBeforeInitialization执行...");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Order值为0的postProcessAfterInitialization执行...");
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
public class Processor1 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Order值为1的postProcessBeforeInitialization执行...");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Order值为1的postProcessAfterInitialization执行...");
return bean;
}
@Override
public int getOrder() {
return 1;
}
}
<!-- xml配置文件 -->
<beans ....>
<bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>
<bean id="processor0" class="cn.example.spring.boke.Processor0"></bean>
<bean id="processor1" class="cn.example.spring.boke.Processor1"></bean>
</beans>
//启动容器,输出如下
Order值为0的postProcessBeforeInitialization执行...
Order值为1的postProcessBeforeInitialization执行...
Order值为0的postProcessAfterInitialization执行...
Order值为1的postProcessAfterInitialization执行...
由上可见,getOrder返回值越小,自定义的后置处理器就越先执行,同时也可以发现,post-processor bean全部作用于普通bean,即一个post-processor bean不会作用于容器中的另一个post-processor bean
(3) IOC容器会自动检测容器中的post-processor bean并提前实例化,以作用于容器中的其他bean,当我们向一个容器中注入了一个BeanPostProcessor,那么该BeanPostProcessor仅对该容器中的bean进行后置处理,例如,即父容器中的BeanPostProcessor不会作用于子容器中的bean
(4) BeanPostProcessor作用于bean实例化并配置好了之后,换句话说,在BeanPostProcessor起作用时,bean实例已经存在了,因此,如果我们想要修改bean的配置元数据(即BeanDefinition,此时的bean还未被创建),则需要实现BeanFactoryPostProcessor接口,它与BeanPostProcessor类似,只不过作用时机不同
(5) 可通过容器ConfigurableBeanFactory中的addBeanPostProcessor方法编程式的向容器注册一个BeanPostProcessor实例,但需要注意,通过该方法注入的BeanPostProcessor实例不会遵守Ordered接口,即如果是编程式的向容器中注册BeanPostProcessor实例,那么其注册的顺序就是后置处理器实际执行的顺序,如下所示
<!-- 其余不变,修改xml文件如下 -->
<beans ...>
<!-- 注意这里设置了懒加载属性,因为我们是要编程式的添加BeanPostProcessor,故而要先创建容器,再调用容器的addBeanPostProcessor方法,但singleton bean会随容器创建而被创建,此时addBeanPostProcessor方法还未被执行,因此BeanPostProcessor就无法作用于这些提前创建bean了,所以我们设置为懒加载或不用singleton作用域 -->
<bean id="exampleA" class="com.lb.spring5.ExampleA" lazy-init="true"></bean>
</beans>
//测试,先注册Processor1,再注册Processor0
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
ctx.getBeanFactory().addBeanPostProcessor(new Processor1());
ctx.getBeanFactory().addBeanPostProcessor(new Processor0());
ctx.getBean(ExampleA.class);
//打印结果,可以发现先注册的BeanPostProcessor先执行
Order值为1的postProcessBeforeInitialization执行...
Order值为0的postProcessBeforeInitialization执行...
Order值为1的postProcessAfterInitialization执行...
Order值为0的postProcessAfterInitialization执行...
(6) BeanPostProcessor与自动代理
在Spring官方文档中,提到了这么一句话:Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them. 这句话的大意就是AOP是基于BeanPostProcessor实现的,因此我们无法对BeanPostProcessor实例及其依赖的bean进行自动代理,织入通知,如下例所示
//定义一个切面MyAspect,对下面的BeanPostProcessor实例进行织入
public class MyAspect {
// 前置通知
public void before(){
System.out.println("before...");
}
}
//定义一个Bean后置处理器Processor,在该处理器中我们定义了一个方法aop,作为切入点,进行织入
public class Processor implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public int getOrder() {
return 0;
}
//切入点
public void aop() {
System.out.println("Processor aop...");
}
}
<!-- xml配置文件 -->
<beans ....>
<!-- 切面 -->
<bean id="myAspect" class="cn.example.spring.boke.MyAspect"></bean>
<!-- aop配置,对指定方法进行切入 -->
<aop:config>
<aop:aspect id="advice" ref="myAspect">
<!-- 声明前置通知 -->
<aop:before method="before" pointcut="execution(public * cn.example.spring.boke.*.aop(..))"></aop:before>
</aop:aspect>
</aop:config>
<bean id="processor" class="cn.example.spring.boke.Processor"></bean>
</beans>
//测试
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
ctx.getBean(Processor.class).aop();
//控制台输出如下,可见前置通知before方法并未执行,此时aop失效
Processor aop...
//然后我们将上面的Processor类变更一下,使它成为一个普通的bean,如下
public class Processor {
public void aop() {
System.out.println("Processor aop...");
}
}
//此时,再启动容器,控制台打印如下,可见,如果是一个普通bean的话,aop是生效的
before...
Processor aop...
对BeanPostProcessor所依赖的bean,它的aop不会生效吗? 答案是不一定,有些情况下会生效,有些情况下不会生效,得结合实际情况具体分析,最好详看这一方面的源码,例子如下
//定义一个普通的bean exampleA,用于测试aop织入
public class ExampleA {
public void aop() {
System.out.println("ExampleA aop...");
}
}
//对于上面的例子,我们把Processor修改一下,让它依赖一个bean exampleA
public class Processor implements BeanPostProcessor,Ordered {
private ExampleA exampleA;
public void setExampleA(ExampleA exampleA) {
this.exampleA = exampleA;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
<!-- xml配置文件 -->
<beans ....>
<!-- 切面保持不变 -->
<bean id="myAspect" class="cn.example.spring.boke.MyAspect"></bean>
<aop:config>
<aop:aspect id="advice" ref="myAspect">
<aop:before method="before" pointcut="execution(public * cn.example.spring.boke.*.aop(..))"></aop:before>
</aop:aspect>
</aop:config>
<bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>
<!-- 通过自动装配机制,向容器中注入依赖项 -->
<bean id="processor" class="cn.example.spring.boke.Processor" autowire="byType"></bean>
</beans>
//测试
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
ctx.getBean(ExampleA.class).aop();
//打印结果如下,aop前置通知未能进行织入成功
ExampleA aop...
//此时,Spring也会给出一条提醒:Bean 'exampleA' of type [cn.example.spring.boke.ExampleA] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying), 提示我们的Bean exampleA可能未被所有的BeanPostProcessor处理
//对于上面的Processor类,我们移除掉Ordered接口,只实现BeanPostProcessor接口,其他属性和方法保持不变
public class Processor implements BeanPostProcessor {
private ExampleA exampleA;
public void setExampleA(ExampleA exampleA) {
this.exampleA = exampleA;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
//这时,再次启动容器,观察控制台打印,虽然这次Spring也给出了一条关于exampleA可能未被所有的BeanPostProcessor处理的提醒,但可以发现aop前置通知成功执行,说明对BeanPostProcessor实例所依赖的bean,是可以进行自动代理的
before...
ExampleA aop...
总结一下: Spring官方文档,只是笼统的说明了一下:无法对BeanPostProcessor实例及其依赖的bean进行自动代理,织入通知. 然而实际上bean是否会被执行自动代理,与BeanPostProcessor何时被Spring处理有着很大联系,至于具体的机制,详见源码
2.通过BeanFactoryPostProcessor来自定义bean的配置元数据
(1) BeanFactoryPostProcessor用于读取容器中bean的配置元数据,并在这些bean被实例化之前来修改它们的配置元数据(此时bean还未被创建),同上面所提及的BeanPostProcessor,BeanFactoryPostProcessor也仅对它所属容器中的bean进行处理,同时我们也可以向容器中注入多个BeanFactoryPostProcessor实例并通过实现Ordered接口来规定它们的执行顺序,如下所示
//实现BeanFactoryPostProcessor接口,该接口提供了一个方法,用于获取配置BeanDefinition
public class Processor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//获取到exampleA的BeanDefinition,并为其name属性赋予值exampleAAA
configurableListableBeanFactory.getBeanDefinition("exampleA").getPropertyValues().addPropertyValue("name", "exampleAAA");
}
}
//普通的bean
public class ExampleA {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
<!-- xml配置文件 -->
<beans ....>
<bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>
<!-- BeanFactoryPostProcessor实例也需要注入到容器中 -->
<bean id="processor" class="cn.example.spring.boke.Processor"></bean>
</beans>
//启动容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
System.out.println(ctx.getBean(ExampleA.class).getName());
//打印日志如下,可见BeanFactoryPostProcessor实例成功执行,向我们的bean中注入了属性值
exampleAAA
将上面的Processor类稍微变更一下,其它保持不变
public class Processor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//增添了一个getBean方法,获取一下容器中的bean exampleA
configurableListableBeanFactory.getBean("exampleA");
configurableListableBeanFactory.getBeanDefinition("exampleA").getPropertyValues().addPropertyValue("name", "exampleAAA");
}
}
//之后,我们重新启动容器,控制台打印如下,可见,此时属性注入失效,这是因为我们使用了getBean方法,导致容器过早的实例化了bean exampleA,从而违反了容器标准的生命周期,绕过了后置处理器
null
3.通过FactoryBean来自定义bean的实例化逻辑
(1) FactoryBean,即工厂Bean,使用了工厂模式,一般用于生产初始化过程很复杂的bean,从而避免编写冗杂的xml配置文件,如下例所示
//一个普通的bean
public class ExampleA {}
//实现FactoryBean接口,作为ExampleA的工厂,用于生成ExampleA实例,该接口主要提供了3个方法如下所示
public class ExampleAFactory implements FactoryBean {
/**
* 返回该工厂所生产的bean实例
*/
@Override
public Object getObject() throws Exception {
return new ExampleA();
}
/**
* 返回该工厂所生产的bean实例的类型
*/
@Override
public Class<?> getObjectType() {
return ExampleA.class;
}
/**
* 返回的bean实例是否为单例
*/
@Override
public boolean isSingleton() {
return true;
}
}
<!-- xml配置文件 -->
<beans ....>
<!-- 将工厂Bean注入进容器中 -->
<bean id="exampleAFactory" class="cn.example.spring.boke.ExampleAFactory"></bean>
</beans>
//启动容器,获取这个工厂Bean
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
System.out.println(ctx.getBean("exampleAFactory"));
//打印结果如下,我们可以发现,我们向容器中注入的是工厂Bean ExampleAFactory实例,结果getBean方法取到的不是这个工厂Bean,而是它所生产的ExampleA
cn.example.spring.boke.ExampleA@1a968a59
//当然,我们也可以获取到这个工厂Bean,而非它所生产的实例,如下,只需在它的name前加一个&符号即可
System.out.println(ctx.getBean("&exampleAFactory"));
//打印结果如下,可见这次我们获取到的是工厂Bean ExampleAFactory本身
cn.example.spring.boke.ExampleAFactory@1a968a59
用户点评