8.1 两种后处理器 8.1.1 Bean后处理器

8.1 两种后处理器

Spring框架提供了很好的扩展性,除了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展,这种扩展甚至无须实现BeanFactoryApplicationContext接口,而是允许通过两个后处理器对loC容器进行扩展。 Spring提供了两种常用的后处理器。

  1. Bean后处理器:这种后处理器会对容器中的Bean进行后处理,对Bean进行额外加强。
  2. 容器后处理器:这种后处理器对IoC容器进行后处理,用于增强容器功能。

下面将介绍这两种常用的后处理器,以及两种后处理器的相关知识。

8.1.1 Bean后处理器

Bean后处理器是一种特殊的Bean,这种特殊的Bean并不对外提供服务,它甚至无须id属性,它主要负责对容器中的其他Bean执行后处理,例如为容器中的目标Bean生成代理等,这种Bean被称为Bean后处理器.
Bean后处理器会在Bean实例创建成功之后,对Bean实例进行进一步的增强处理。

Bean后处理器必须实现BeanPostProcessor接口, BeanPostProcessor接口包含如下两个方法。

BeanPostProcessor接口方法 描述
Object postProcessBeforelnitialization(Object bean, String name) throws Beans Exception 在目标Bean初始化之前,对容器中的Bean实例进行增强处理。该方法的第一个参数是系统即将进行后处理的Bean实例,第二个参数是该Bean的配置id
Object postProcessAfterInitialization(Object bean,String name) throws Beans Exception 在目标Bean初始化之后,对容器中的Bean实例进行增强处理。该方法的第一个参数是系统即将进行后处理的Bean实例,第二个参数是该Bean的配置id.

实现该接口的Bean后处理器必须实现这两个方法。

Bean后处理器是对loC容器一种极好的扩展,Bean后处理器可以对容器中的Bean进行后处理,而到底要对Bean进行怎样的后处理则完全取决于开发者。 Spring容器负责把各Bean创建出来,而开发者提供的Bean后处理器可以依次对每个Bean进行某种修改、増强,从而可以对容器中的Bean集中増加某种功能.

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\BeanPostProcessor
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─service\
│ ├─Axe.java
│ ├─impl\
│ │ ├─Chinese.java
│ │ └─SteelAxe.java
│ └─Person.java
└─util\
└─MyBeanPostProcessor.java

下面将定义一个简单的Bean后处理器,该Bean后处理器将对容器中的其他Bean进行后处理。Bean后处理器的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package org.crazyit.app.util;

import java.lang.reflect.Field;
import org.crazyit.app.service.impl.Chinese;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor
{
/**
* 对容器中的Bean实例进行后处理
* @param bean 需要进行后处理的原Bean实例
* @param beanName 需要进行后处理的Bean的配置id
* @return 返回后处理完成后的Bean
*/
public Object postProcessBeforeInitialization(Object bean, String beanName)
{
System.out.println("Bean后处理器在初始化 之前 对" + beanName + "进行增强处理...");
// 返回的处理后的Bean实例,该实例就是容器中实际使用的Bean
// 该Bean实例甚至可与原Bean截然不同
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName)
{
System.out.println("Bean后处理器在初始化 之后 对" + beanName + "进行增强处理...");
// 如果该Bean是Chinese类的实例
if (bean instanceof Chinese)
{
try
{
// 通过反射修改其name成员变量
Class<?> clazz = bean.getClass();
Field f = clazz.getDeclaredField("name");
f.setAccessible(true);
f.set(bean, "FKJAVA:" + f.get(bean));
} catch (Exception ex)
{
ex.printStackTrace();
}
}
return bean;
}
}

上面程序中实现了对Bean进行增强处理的逻辑,当Spring容器实例化Bean实例之后,就会依次调用Bean后处理器的两个方法对Bean实例进行增强处理。
下面是Chinese Bean类的代码,该类实现了InitializingBean接口(并实现了该接口包含的afterPropertiesSet()方法),还额外提供了一个初始化方法(init()方法),这两个方法都用于定制该Bean实例的生命周期行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package org.crazyit.app.service.impl;

import org.crazyit.app.service.Axe;
import org.crazyit.app.service.Person;
import org.springframework.beans.factory.InitializingBean;

public class Chinese implements Person, InitializingBean
{
private Axe axe;
private String name;
public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
public void setAxe(Axe axe)
{
this.axe = axe;
}
public void setName(String name)
{
System.out.println("Spring执行setName()方法注入依赖关系...");
this.name = name;
}
public void useAxe()
{
System.out.println(name + axe.chop());
}
// 下面是两个生命周期方法
public void init()
{
System.out.println("正在执行初始化方法 init...");
}
public void afterPropertiesSet() throws Exception
{
System.out.println("正在执行初始化方法 afterPropertiesSet...");
}
}

在配置文件中配置Bean后处理器和配置普通Bean完全一样,但有一点需要指出,通常程序无须主动获取Bean后处理器,因此配置文件可以无须为Bean后处理器指定id属性。下面的配置文件因为需要手动注册Bean后处理器,所以配置文件依然为Bean后处理器指定了id属性。配置文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置2个普通Bean实例 -->
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
init-method="init"
p:axe-ref="steelAxe" p:name="依赖注入的值" />
<!-- 配置Bean后处理器,可以无需指定id属性 -->
<bean id="bp" class="org.crazyit.app.util.MyBeanPostProcessor" />
</beans>

上面文件的粗体字代码配置了一个Bean后处理器,这个Bean后处理器将会对容器中的所有Bean实例进行后处理。为了更好地观察到Bean后处理器的后处理方法的执行时机,程序还为chinese bean指定了如下两个初始化方法。

  1. init-method指定初始化方法
  2. 实现InitializingBean接口,提供了afterPropertiesSet初始化方法。

该示例的主程序如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package lee;

import org.crazyit.app.service.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 以类加载路径下的beans.xml文件来创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
Person p = (Person) ctx.getBean("chinese");
p.useAxe();
}
}

ApplicationContext作为Spring容器时会自动注册Bean后处理器

从上面代码可以看出,该程序根本看不到任何关于Bean后处理器的代码,这是因为:如果使用ApplicationContext作为Spring容器, Spring容器会自动检测容器中的所有Bean,如果发现某个Bean类实现了BeanPostProcessor接口, ApplicationContext会自动将其注册为Bean后处理器。
运行上面的程序,可以看到如下运行结果:

1
2
3
4
5
6
7
8
9
10
Spring实例化依赖bean:SteelAxe实例...
Bean后处理器在初始化 之前 对steelAxe进行增强处理...
Bean后处理器在初始化 之后 对steelAxe进行增强处理...
Spring实例化主调bean:Chinese实例...
Spring执行setName()方法注入依赖关系...
Bean后处理器在初始化 之前 对chinese进行增强处理...
正在执行初始化方法 afterPropertiesSet...
正在执行初始化方法 init...
Bean后处理器在初始化 之后 对chinese进行增强处理...
FKJAVA:依赖注入的值钢斧砍柴真快

从上面的执行结果可以看出,虽然配置文件中指定chinese这个Beanname为”依赖注入的值”,但该chinese这个Beanname成员变量的值被修改了,增加了"FKJAVA:"前缀——这就是Bean后处理器的作用。
容器中一旦注册了Bean后处理器,Bean后处理器就会自动启动,在容器中每个Bean创建时自动工作,加入Bean后处理器需要完成的工作。从上面的执行过程可以看出,Bean后处理器两个方法的回调时机如图8.1所示。
这里有一张图片
实现BeanPostProcessor接口的Bean后处理器可对Bean进行任何操作,包括完全忽略这个回调。BeanPostprocessor通常用来检查标记接口,或者做如将Bean包装成一个Proxy的事情, Spring的很多工具类就是通过Bean后处理器完成的。

BeanFactory作为Spring容器时必须手动注册Bean

如果使用BeanFactory作为Spring容器,则必须手动注册Bean后处理器,程序必须获取Bean后处理器实例,然后手动注册。在这种需求下,程序可能需要在配置文件中为Bean处理器指定id属性,这样才能让Spring容器先获取Bean后处理器,然后注册它。因此,如果使用BeanFactory作为Spring容器,则需要将主程序改为如下形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package lee;

import org.crazyit.app.service.Person;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 搜索类加载路径下的beans.xml文件创建Resource对象
Resource isr = new ClassPathResource("beans.xml");
// 创建默认的BeanFactory容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 让默认的BeanFactory容器加载isr对应的XML配置文件
new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(isr);
// 获取容器中的Bean后处理器
BeanPostProcessor bp = (BeanPostProcessor) beanFactory.getBean("bp");
// 注册Bean后处理器
beanFactory.addBeanPostProcessor(bp);
Person p = (Person) beanFactory.getBean("chinese");
p.useAxe();
}
}

正如从上面粗体字代码所看到的,程序中bp就是Bean后处理器配置id,一旦程序获取了Bean后处理器,即可调用BeanFactoryaddBeanPostProcessor()方法来注册该Bean后处理器。