(转)6.1 两种后处理器 .
本章要点
利用后处理器扩展Spring容器
AOP的基本概念
如何生成AOP代理
自动生成AOP代理
Spring的事务策略
配置简化的事务代理和自动事务代理
Spring整合Struts
Spring整合Hibernate
上一章已经介绍了Spring框架的基础内容,详细介绍了Spring容器的核心机制——依赖注入,并介绍了Spring容器对Bean的管理。实际上,上一章介绍的内容是大部分项目都需要使用的基础部分,很多时候,即使不使用Spring框架,实际项目都会采用相同的策略。
但Spring框架的功能绝不是只有这些部分,Spring框架允许开发者使用两种后处理器扩展IoC容器,这两种后处理器可以后处理IoC容器本身或对容器中所有的Bean进行后处理。IoC容器还提供了AOP功能,极好地丰富了Spring容器的功能。本章将更深入地介绍这些内容,并详细介绍Spring与Hibernate和Struts框架的整合。
6.1两种后处理器
Spring框架提供了很好的扩展性,除了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展。这种扩展并不是通过实现BeanFactory或ApplicationContext的子类,而是通过两个后处理器对IoC容器进行扩展。Spring提供了两种常用的后处理器:
●Bean后处理器,这种后处理器会对容器中特定的Bean进行定制,例如功能的加强。
●容器后处理器,这种后处理器对IoC容器进行特定的后处理。
下面将介绍这两种常用的后处理器以及两种后处理器相关知识。
6.1.1Bean后处理器
Bean后处理器是一种特殊的Bean,这种特殊的Bean并不对外提供服务,它无须id属性,但它负责对容器中的其他Bean执行后处理,例如为容器中的目标Bean生成代理。这种Bean可称为Bean后处理器,它在Bean实例创建成功后,对其进行进一步的加强处理。
Bean后处理器必须实现BeanPostProcessor接口。
BeanPostProcessor接口包含两个方法:
●ObjectpostProcessBeforeInitialization(Objectbean,Stringname)throwsBeansExce-ption,该方法的第一个参数是系统即将初始化的Bean实例,第二个参数是Bean实例的名字。
●ObjectpostProcessAfterInitialization(Objectbean,Stringname)throwsBeansExce-ption,该方法的第一个参数是系统刚完成初始化的Bean实例,第二个参数是Bean实例的名字。
实现该接口的Bean必须实现这两个方法,这两个方法会对容器的Bean进行后处理。两个方法会在目标Bean初始化之前和初始化之后分别调用。这两个方法用于对系统完成的默认初始化进行加强。
注意:Bean后处理器是对IoC容器一种极好的扩展,Bean后处理器可以对容器中的Bean进行后处理,这种后处理完全由开发者决定。
下面将定义一个简单的Bean后处理器,该Bean后处理器将对容器中其他Bean进行后处理。Bean后处理器的代码如下:
//自定义Bean后处理器,负责后处理容器中所有的Bean
publicclassMyBeanPostProcessorimplementsBeanPostProcessor
{
//在初始化bean之前,调用该方法
publicObjectpostProcessBeforeInitialization(Objectbean,String
beanName)throwsBeansException
{
//仅仅打印一行字符串
System.out.println("系统正在准备对"+beanName+"进行初始化...");
returnbean;
}
//在初始化bean之后,调用该方法
publicObjectpostProcessAfterInitialization(Objectbean,String
beanName)throwsBeansException
{
System.out.println("系统已经完成对"+beanName+"的初始化");
//如果系统刚完成初始化的bean是Chinese
if(beaninstanceofChinese)
{
//为Chinese实例设置name属性
Chinesec=(Chinese)bean;
c.setName("wawa");
}
returnbean;
}
}
下面是Chinese的源代码,该类实现了InitializingBean接口,还额外提供了一个初始化方法,这两个方法都由Spring容器控制回调。
publicclassChineseimplementsPerson,InitializingBean
{
privateAxeaxe;
privateStringname;
publicChinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
publicvoidsetAxe(Axeaxe)
{
System.out.println("Spring执行依赖关系注入...");
this.axe=axe;
}
publicvoidsetName(Stringname)
{
this.name=name;
}
publicvoiduseAxe()
{
System.out.println(name+axe.chop());
}
publicvoidinit()
{
System.out.println("正在执行初始化方法init...");
}
publicvoidafterPropertiesSet()throwsException
{
System.out.println("正在执行初始化方法afterPropertiesSet...");
}
}
配置文件如下:
<?xmlversion="1.0"encoding="gb2312"?>
<!--指定Spring配置文件的dtd>
<!DOCTYPEbeansPUBLIC"-//SPRING//DTDBEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!--Spring配置文件的根元素-->
<beans>
<!--配置bean后处理器,可以没有id属性,此处id属性为了后面引用-->
<beanid="beanPostProcessor"class="lee.MyBeanPostProcessor"/>
<beanid="steelAxe"class="lee.SteelAxe"/>
<beanid="chinese"class="lee.Chinese"init-method="init">
<propertyname="axe"ref="steelAxe"/>
</bean>
</beans>
本应用的chinese具有两个初始化方法:
●init-method指定初始化方法。
●实现InitializingBean接口,提供了afterPropertiesSet初始化方法。
MyBeanPostProcessor类实现了BeanPostProcessor接口,并实现了该接口的两个方法,这两个方法分别在初始化方法调用之前和之后得到回调。
注意:上面的配置文件配置Bean后处理器时,依然为Bean处理器指定了id属性,指定id属性是为了方便程序通过该id属性访问Bean后处理器。大部分时候,程序无须手动访问该Bean后处理器,因此无须为其指定id属性。
主程序如下:
publicclassBeanTest
{
publicstaticvoidmain(String[]args)throwsException
{
//CLASSPATH路径下的bean.xml文件创建Resource对象
ClassPathResourceisr=newClassPathResource("bean.xml");
//以Resource对象作为参数,创建BeanFactory的实例
XmlBeanFactoryfactory=newXmlBeanFactory(isr);
//获取Bean后处理器实例
MyBeanPostProcessorbeanProcessor=
(MyBeanPostProcessor)factory.getBean("beanPostProcessor");
//注册BeanPostProcessor实例
factory.addBeanPostProcessor(beanProcessor);
System.out.println("程序已经实例化BeanFactory...");
Personp=(Person)factory.getBean("chinese");
System.out.println("程序中已经完成了chinesebean的实例化...");
p.useAxe();
}
}
如果使用BeanFactory作为Spring容器,必须手动注册Bean后处理器,因此在程序中先获取Bean后处理器实例,然后手动注册——这就是在配置文件中指定Bean后处理器id属性的原因。通过BeanFactory的addBeanPostProcessor可以注册BeanPostProcessor实例。程序执行结果如下:
[java]程序已经实例化BeanFactory...
[java]Spring实例化主调bean:Chinese实例...
[java]Spring实例化依赖bean:SteelAxe实例...
[java]系统正在准备对steelAxe进行初始化...
[java]系统已经完成对steelAxe的初始化
[java]Spring执行依赖关系注入...
[java]系统正在准备对chinese进行初始化...
[java]正在执行初始化方法afterPropertiesSet...
[java]正在执行初始化方法init...
[java]系统已经完成对chinese的初始化
[java]程序中已经完成了chinesebean的实例化...
[java]wawa钢斧砍柴真快
在配置文件中配置chinese实例时,并未指定name属性值。但程序执行时,name属性有了值,这就是Bean后处理器完成的,在Bean后处理器中判断Bean是否是Chinese实例,然后设置它的name属性。
容器中一旦注册了Bean后处理器,Bean后处理器会自动启动,在容器中每个Bean创建时自动工作,完成加入Bean后处理器需要完成的工作。
实现BeanPostProcessor接口的Bean后处理器可对Bean进行任何操作,包括完全忽略这个回调。BeanPostProcessor通常用来检查标记接口或将Bean包装成一个Proxy的事情。Spring的很多工具类,就是通过Bean后处理器完成的。
从主程序中看到,采用BeanFactory作为Spring容器时,必须手动注册BeanPost-Processor。而对于ApplicationContext,则无须手动注册。ApplicationContext可自动检测到容器中的Bean后处理器,自动注册。Bean后处理器会在Bean实例创建时,自动启动。即主程序采用如下代码,效果完全一样:
publicclassBeanTest
{
publicstaticvoidmain(String[]args)throwsException
{
ApplicationContextctx=newClassPathXmlApplicationContext
("bean.xml");
Personp=(Person)factory.getBean("chinese");
System.out.println("程序中已经完成了chinesebean的实例化...");
p.useAxe();
}
}
使用ApplicationContext作为容器,无须手动注册BeanPostProcessor。因此,如果需要使用Bean后处理器,Spring容器建议使用ApplicationContext,而不是BeanFactory。
6.1.2Bean后处理器的用处
上一节介绍了一个简单的Bean后处理器,上面的Bean后处理器负责对容器中的ChineseBean进行后处理,不管ChineseBean如何初始化,总是将ChineseBean的name属性设置为wawa。这种后处理看起来作用并不是特别大。
实际上,Bean后处理器完成的工作更加实际,例如生成Proxy。Spring框架本身提供了大量的Bean后处理器,这些后处理器负责对容器中的Bean进行后处理。
下面是Spring提供的两个常用的后处理器:
●BeanNameAutoProxyCreator,根据Bean实例的name属性,创建Bean实例的代理。
●DefaultAdvisorAutoProxyCreator,根据提供的Advisor,对容器中所有的Bean实例创建代理。
上面提供的两个Bean后处理器,都用于根据容器中配置的拦截器创建目标Bean代理,目标代理就在目标Bean的基础上修改得到。
注意:如果需要对容器中某一批Bean进行特定的处理,可以考虑使用Bean后处理器。
6.1.3容器后处理器
除了上面提供的Bean后处理器外,Spring还提供了一种容器后处理器。Bean后处理器负责后处理容器生成的所有Bean,而容器后处理器则负责后处理容器本身。
容器后处理器必须实现BeanFactoryPostProcessor接口。实现该接口必须实现如下一个方法:
voidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)
实现该方法的方法体就是对Spring容器进行的处理,这种处理可以对Spring容器进行任意的扩展,当然也可以对Spring容器不进行任何处理。
类似于BeanPostProcessor,ApplicationContext可自动检测到容器中的容器后处理器,并且自动注册容器后处理器。但若使用BeanFactory作为Spring容器,则必须手动注册后处理器。
下面定义了一个容器后处理器,这个容器后处理器实现BeanFactoryPostProcessor接口,但并未对Spring容器进行任何处理,只是打印出一行简单的信息。该容器后处理器的代码如下:
publicclassMyBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor
{
//容器后处理器对容器进行的处理在该方法中实现
publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory)
throwsBeansException
{
System.out.println("程序对Spring所做的BeanFactory的初始化没有意
见...");
}
}
将该Bean作为普通Bean部署在容器中,然后使用ApplicationContext作为容器,容器会自动调用BeanFactoryPostProcessor处理Spring容器。程序执行效果如下:
[java]程序对Spring所做的BeanFactory的初始化没有意见...
实现BeanFactoryPostProcessor接口的Bean后处理器不仅可对BeanFactory执行后处理,也可以对ApplicationContext容器执行后处理。容器后处理器还可用来注册额外的属性编辑器。
注意:Spring没有提供ApplicationContextPostProcessor。也就是说,对于Application-Context容器,一样使用BeanFactoryPostProcessor作为容器后处理器。
Spring已提供如下两个常用的容器后处理器,包括:
●PropertyResourceConfigurer,属性占位符配置器。
●PropertyPlaceHolderConfigurer,另一种属性占位符配置器。
下面将详细介绍这两种常用的容器后处理器。
6.1.4属性占位符配置器
Spring提供了PropertyPlaceholderConfigurer,它是一个容器后处理器,负责读取Java属性文件里的属性值,并将这些属性值设置到Spring容器定义中。
通过使用PropertyPlaceholderConfigurer后处理器,可以将Spring配置文件中的部分设置放在属性文件中设置。这种配置方式当然有其优势:可以将部分相似的配置(如数据库的urls、用户名和密码)放在特定的属性文件中,如果只需要修改这部分配置,则无须修改Spring配置文件,修改属性文件即可。
下面的配置文件配置了PropertyPlaceholderConfigurer后处理器,在配置数据源Bean时,使用了属性文件中的属性值。配置文件的代码如下:
<?xmlversion="1.0"encoding="GBK"?>
<!--beans是Spring配置文件的根元素,并且指定了Schema信息-->
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置一个容器后处理器Bean-->
<beanid="propertyConfigurer"
class="org.springframework.beans.factory.config.
PropertyPlaceholderConfigurer">
<!--locations属性指定属性文件的位置-->
<propertyname="locations">
<list>
<value>dbconn.properties</value>
<!--如果有多个属性文件,依次在下面列出来-->
</list>
</property>
</bean>
<!--定义数据源Bean,使用C3P0数据源实现-->
<beanid="dataSource"class="com.mchange.v2.c3p0.
ComboPooledDataSource"destroy-method="close">
<!--指定连接数据库的驱动-->
<propertyname="driverClass"value="${jdbc.driverClassName}"/>
<!--指定连接数据库的URL-->
<propertyname="jdbcUrl"value="${jdbc.url}"/>
<!--指定连接数据库的用户名-->
<propertyname="user"value="${jdbc.username}"/>
<!--指定连接数据库的密码-->
<propertyname="password"value="${jdbc.password}"/>
</bean>
</beans>
在上面的配置文件中,配置driverClass和jdbcUrl等信息时,并未直接设置这些属性的属性值,而是设置了${jdbc.driverClassName}和${jdbc.url}属性值。这表明Spring容器将从propertyConfigurer指定属性文件中搜索这些key对应的value,并为该Bean的属性值设置这些value值。
如前所述,ApplicationContext会自动检测部署在容器的容器后处理器,无须额外的注册,容器自动注册。因此,只需提供如下JavaProperties文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/j2ee
jdbc.username=root
jdbc.password=32147
通过这种方法,可从主XML配置文件中分离出部分配置信息。如果仅需要修改数据库连接属性,则无须修改主XML配置文件,只需要修改属性文件即可。采用属性占位符的配置方式,可以支持使用多个属性文件。通过这种方式,可将配置文件分割成多个属性文件,从而降低修改配置的风险。
注意:对于数据库连接等信息集中的配置,可以将其配置在Java属性文件中,但不要过多地将Spring配置信息抽离到Java属性文件中,否则可能会降低Spring配置文件的可读性。
6.1.5另一种属性占位符配置器(PropertyOverrideConfigurer)
PropertyOverrideConfigurer是Spring提供的另一个容器后处理器,这个后处理器的额作用与上面介绍的容器后处理器作用大致相同。但也存在些许差别:PropertyOverride-Configurer使用的属性文件用于覆盖XML配置文件中的定义。即PropertyOverride-Configurer允许XML配置文件中有默认的配置信息。
如果PropertyOverrideConfigurer的属性文件有对应配置信息,XML文件中的配置信息被覆盖;否则,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的属性文件,应是如下的格式:
beanName.property=value
beanName是属性占位符试图覆盖的Bean名,property是试图覆盖的属性名。看如下配置文件:
<?xmlversion="1.0"encoding="GBK"?>
<!--beans是Spring配置文件的根元素,并且指定了Schema信息-->
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置一个属性占位符Bean。ApplictionContext能自动识别
PropertyPlaceholderConfigurerBean-->
<beanid="propertyOverrider"
class="org.springframework.beans.factory.config.
PropertyOverrideConfigurer">
<propertyname="locations">
<list>
<value>dbconn.properties</value>
<!--如果有多个属性文件,依次在下面列出来-->
</list>
</property>
</bean>
<!--定义数据源Bean,使用C3P0数据源实现-->
<beanid="dataSource"class="com.mchange.v2.c3p0.
ComboPooledDataSource"destroy-method="close">
<!--指定连接数据库的驱动-->
<propertyname="driverClass"value="dd"/>
<!--指定连接数据库的URL-->
<propertyname="jdbcUrl"value="xx"/>
<!--指定连接数据库的用户名-->
<propertyname="user"value="dd"/>
<!--指定连接数据库的密码-->
<propertyname="password"value="xx"/>
</bean>
</beans>
上面的配置文件中,指定数据源Bean的各种属性值时,只是随意指定了几个属性值,很明显通过这几个属性值无法连接到数据库服务。
但因为Spring容器中部署了一个PropertyOverrideConfigurer的容器后处理器,而且Spring容器使用ApplicationContext作为容器,它会自动检测容器中的容器后处理器,无须额外的注册,容器自动注册该后处理器。
PropertyOverrideConfigurer后处理器读取dbconn.properties文件中的属性,用于覆盖目标Bean的属性。因此,如果属性文件中有dataSourceBean属性的设置,则配置文件中指定的属性值将没有任何作用。
dbconn.properties属性文件如下:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://wonder:3306/j2ee
dataSource.username=root
dataSource.password=32147
注意属性文件的格式必须是:
beanName.property=value
也就是说,dataSource必须是容器中真实存在的bean名,否则程序将出错。
注意:程序无法知道BeanFactory定义是否被覆盖。仅仅通过察看XML配置文件,无法知道配置文件的配置信息是否被覆盖。如有多个PorpertyOverrideConfigurer对同一Bean属性定义了覆盖,最后一个覆盖获胜。