http://blog.csdn.net/topwqp/article/details/8696897
上一个例子演示了对特定的bean中的所有的方法进行面向切面编程,包括了 before , after , after throwing, around 几种形式:
如果想对一个bean中的特定方法进行切面编程,而不是所有的方法,就需要设置pointcut了,pointcut允许拦截一个方法通过 方法名 ,一个 pointcut必须和一个advisor想关联。
一般有以下配置组成:
1:advice 在方法执行前(before)后(after)做出相应的响应。通常是定义一些实现接口的类,然后实现相应的方法,比如:before 对应的实现MethodBeforeAdvice接口 , after对应的实现AfterReturningAdvice , around对应的实现MethodInterceptor接口 , after throwing 对应的实现:ThrowsAdvice 接口, 实现对应的接口的方法即可。
Pointcut 运用的例子:
- <!-- define a pointcut -->
- <bean id="bookPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
- <property name="mappedName" value="printName" />
- </bean>
2:定义一个advice 相当于 对切点所做的操作, 根据advice的拦截位置需要实现相应的接口,比如: MethodInterceptor 是around对应的接口。
- <!-- around method -->
- bean id="aroundMethod" class="com.myapp.core.aop.advice.AroundMethod" />
对应的类:
- package com.myapp.core.aop.advice;
- import java.util.Arrays;
- import org.aopalliance.intercept.MethodInterceptor;
- import org.aopalliance.intercept.MethodInvocation;
- public class AroundMethod implements MethodInterceptor{
- @Override
- public Object invoke(MethodInvocation methodInvocation) throws Throwable {
- // TODO Auto-generated method stub
- System.out.println("method name:" + methodInvocation.getMethod().getName());
- System.out.println("method arguments" + Arrays.toString(methodInvocation.getArguments()));
- System.out.println("Around method : before ");
- try{
- Object result = methodInvocation.proceed();
- System.out.println("Around method : after ");
- return result;
- }catch(IllegalArgumentException e){
- System.out.println("Around method : throw an exception ");
- throw e;
- }
- }
- }
- <!-- define a advisor -->
- <bean id="bookAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
- <property name="pointcut" ref="bookPointcut"/>
- <property name="advice" ref="aroundMethod"></property>
- </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-3.0.xsd">
- <!-- more bean definitions for data access objects go here -->
- <bean id="book" class="com.myapp.core.aop.advice.Book">
- <property name="name" value="Effective java" />
- <property name="url" value="www.google.cn"/>
- <property name="pages" value="300" />
- </bean>
- <!-- around method -->
- <bean id="aroundMethod" class="com.myapp.core.aop.advice.AroundMethod" />
- <!-- define a pointcut -->
- <bean id="bookPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
- <property name="mappedName" value="printName" />
- </bean>
- <!-- define a advisor -->
- <bean id="bookAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
- <property name="pointcut" ref="bookPointcut"/>
- <property name="advice" ref="aroundMethod"></property>
- </bean>
- <bean id="bookProxy" class="org.springframework.aop.framework.ProxyFactoryBean" >
- <property name="target" ref="book"/>
- <property name="interceptorNames">
- <list>
- <value>bookAdvisor</value>
- </list>
- </property>
- </bean>
- </beans>
对应的测试类:
- package com.myapp.core.aop.advice;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class MainTest {
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext("resource/aop.xml");
- Book book = (Book) context.getBean("bookProxy");
- System.out.println("---------------------");
- book.printName();
- System.out.println("---------------------");
- book.printUrl();
- System.out.println("----------------------");
- try{
- book.printThrowException();
- }catch(Exception e){
- // e.printStackTrace();
- }
- }
- }
测试结果:
- 三月 20, 2013 5:37:23 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
- INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@eb67e8: startup date [Wed Mar 20 17:37:23 CST 2013]; root of context hierarchy
- 三月 20, 2013 5:37:23 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
- INFO: Loading XML bean definitions from class path resource [resource/aop.xml]
- 三月 20, 2013 5:37:23 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
- INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@157985: defining beans [book,aroundMethod,bookPointcut,bookAdvisor,bookProxy]; root of factory hierarchy
- ---------------------
- method name:printName
- method arguments[]
- Around method : before
- Book name Effective java
- Around method : after
- ---------------------
- Book URL www.google.cn
- ----------------------
只有在printName方法上作用了around,其他方法没有调用,over。
9.5.3 容器中的织入器--ProxyFactoryBean(1)
虽然使用ProxyFactory,可以让我们能够独立于Spring的IoC容器之外来使用Spring的AOP支持,但是,将Spring AOP与Spring的IoC容器支持相结合,才是发挥Spring AOP更大作用的最佳途径。通过结合Spring的IoC容器,我们可以在容器中对Pointcut和Advice等进行管理,即使它们依赖于其他业务对象,也可以很容易地注入其中。
在IoC容器中,使用org.springframework.aop.framework.ProxyFactoryBean作为织入器,它的使用与ProxyFactory无太大差别。不过在演示ProxyFactoryBean的使用之前,我们有必要在看清了ProxyFactory本质的前提下,进一步弄明白ProxyFactoryBean的本质。
1. ProxyFactoryBean的本质
对于ProxyFactoryBean,我们应该这样断词,即Proxy+FactoryBean,而不是ProxyFactory+ Bean。也就是说,ProxyFactoryBean本质上是一个用来生产Proxy的FactoryBean。还记得IoC容器中的FactoryBean的作用吧?如果容器中的某个对象持有某个FactoryBean的引用,它取得的不是FactoryBean本身,而是FactoryBean的getObject()方法所返回的对象。所以,如果容器中某个对象依赖于ProxyFactoryBean,那么它将会使用到ProxyFactoryBean的getObject()方法所返回的代理对象,这就是ProxyFactryBean得以在容器中游刃有余的原因。
要让ProxyFactoryBean的getObject()方法返回相应目标对象类的代理对象其实很简单。因为ProxyFactoryBean继承了与ProxyFactory共有的父类ProxyCreatorSupport,而ProxyCreator- Support基本上已经把要做的事情(如设置目标对象、配置其他部件、生成对应的AopProxy等)全部完成了。我们只需在ProxyFactoryBean的getObject()方法中通过父类的createAopProxy()取得相应的AopProxy,然后"return AopProxy.getProxy()"即可。
因为涉及FactoryBean,所以在实现getObject()时,逻辑上还得点缀一下。我们来看ProxyFac- toryBean的getObject()定义(见代码清单9-26)。
代码清单9-26 ProxyFactoryBean的getObject()方法逻辑
- public Object getObject() throws BeansException {
- initializeAdvisorChain();
- if (isSingleton()) {
- return getSingletonInstance();
- }
- else
- {
- if (this.targetName == null)
- {
- logger.warn("Using non-singleton proxies
with singleton targets is often undesirable." +?- "Enable prototype proxies by setting the
'targetName' property.");- }
- return newPrototypeInstance();
- }
- }
FactoryBean定义中要求标明返回的对象是以singleton的scope返回,还是以prototype的scope返回。所以,得针对这两种情况分别返回不同的代理对象,以满足FactoryBean的isSingleton()方法的语义。
如果将ProxyFactoryBean的singleton属性设置为true,则ProxyFactoryBean在第一次生成代理对象之后,会通过内部实例变量singletonInstance(Object类型)缓存生成的代理对象。之后,所有的请求将会返回这一缓存实例,从而满足singleton的语义。反之,如果将ProxyFactoryBean的singleton属性设置为false,那么,ProxyFactoryBean每次都会重新检测各项设置,并为当前调用准备一套新的环境,然后再根据最新的环境数据,返回一个新的代理对象。因此,如果singleton属性为false,在生成代理对象的性能上存在损失。如果非要这么做,请确保有充足的理由。singleton默认值为true,即返回同一个代理对象实例。
如果对ProxyFactoryBean的细节感兴趣,可以读一下ProxyFactoryBean的代码。
2. ProxyFactoryBean的使用
与ProxyFactory一样,通过ProxyFactoryBean,我们可以在生成目标对象的代理对象的时候,指定使用基于接口的代理还是基于类的代理方式,而且,因为它们全部继承自同一个父类,大部分可设置项目都相同。不过,ProxyFactoryBean在继承了父类ProxyCreatorSupport的所有配置属性之外,还添加了几个自己独有的,如下所示。
proxyInterfaces。如果我们要采用基于接口的代理方式,那么需要通过该属性配置相应的接口类型,这是一个Collection类型实例,所以我们可以通过配置元素<list>来指定一个或者多个接口类型。实际上,这与通过Interfaces属性指定接口类型是等效的,我们完全可以随个人喜好来使用,虽然使用proxyInterfaces可以保持使用上的统一风格。另外,如果目标对象实现了某个或者多个接口,即使我们不通过该属性指定要代理的接口类型,ProxyFactroyBean也可以自动检测到目标对象所实现的接口,并对其进行基于接口的代理。因为ProxyFactoryBean有一个autodetectInterfaces属性,该属性默认值为true,即如果没有明确指定要代理的接口类型,ProxyFactoryBean会自动检测目标对象所实现的接口类型并进行代理。
interceptorNames。通过该属性,我们可以指定多个将要织入到目标对象的Advice、拦截器以及Advisor,而再也不用通过ProxyFactory那样的addAdvice或者addAdvisor方法一个一个地添加了。因为该属性属于Collection类型,所以通常我们会使用配置元素<list>添加需要的拦截器名称。该属性有两个特性需要提及,如以下所述。
如果没有通过相应的设置目标对象的方法明确为ProxyFactoryBean设置目标对象,那么可以在interceptorNames的最后一个元素位置,放置目标对象的bean定义名称。这是个特例,大部分情况下,还是建议明确指定目标对象,而避免这种配置方式。
通过在指定的interceptorNames某个元素名称之后添加*通配符,可以让ProxyFactory- Bean在容器中搜寻符合条件的所有的Advisor并应用到目标对象。这些符合条件的Advisor,Spring参考文档中称之为global advisor。代码清单9-27给出了这种用法的示例。
singleton。因为ProxyFactoryBean本质上是一个FactoryBean,所以我们可以通过singleton属性,指定每次getObject调用是返回同一个代理对象,还是返回一个新的。通常情况下是返回同一个代理对象,即singleton为true。只有在需要返回有状态的代理对象的情况下,才会将singleton设置为false,如使用Introduction的场合。
代码清单9-27 包含*通配符的interceptorNames属性使用示例
- <bean id="proxy"
- class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="target" ref="..."/>
- <property name="interceptorNames">
- <list>
- <value>global*</value>
- </list>
- </property>
- </bean>
- <bean id="global_debug" ?
- class="org.springframework.aop.interceptor.DebugInterceptor"/>
- <bean id="global_performance" class="org.
springframework.aop.interceptor. ?- PerformanceMonitorInterceptor"/>
要在容器中通过ProxyFactoryBean使用基于接口的代理方式,通常可以采用代码清单9-28所示的配置方式。
代码清单9-28 通过ProxyFactoryBean使用基于接口的代理方式的配置示例
现在,从Pointcut到Advice再到Advisor,从目标对象到相应的代理对象,全部都由IoC容器统一管理。为ProxyFactoryBean指定目标对象、要代理的接口类型以及相应的Advisor或Advice,ProxyFactoryBean就会返回目标对象的代理对象供调用者使用。我们可以将生成的代理对象直接注入到依赖的主体对象中,但是这里有一个初学者容易犯的错误,就是通常会将目标对象task注入依赖的主体对象,而不是目标对象的代理对象taskProxy。通过之前有关代理模式的讲解,现在应该不会犯这种错误了。将没有织入任何横切逻辑的目标对象,而不是代理对象注入依赖的主体对象,一定不会产生任何拦截效果。为了避免这种问题,如果没有依赖于目标对象的依赖关系,可以将目标对象的bean定义声明为内部bean,这样,就不会出现该引用目标对象代理对象的地方,反而因不慎或者其他原因而引用目标对象本身的情况。代码清单9-29演示了这种好的实践方式。
- <bean id="pointcut" class="org.springframework.aop.
support.NameMatchMethodPointcut">- <property name="mappedName" value="execute"/>
- </bean>
- <bean id="performanceInterceptor" class="...
advice.PerformanceMethodInterceptor">- </bean>
- <bean id="performanceAdvisor" class="org.
springframework.aop.support.DefaultPointcutAdvisor">- <property name="pointcut">
- <ref bean="pointcut"/>
- </property>
- <property name="advice">
- <ref bean="performanceInterceptor"/>
- </property>
- </bean>
- <bean id="task" class="...MockTask">
- </bean>
- <bean id="taskProxy" class="org.springframework.
aop.framework.ProxyFactoryBean">- <property name="target">
- <ref bean="task"/>
- </property>
- <property name="proxyInterfaces">
- <list>
- <value>...ITask</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>performanceAdvisor</value>
- </list>
- </property>
- </bean>
9.5.3 容器中的织入器--ProxyFactoryBean(2)
代码清单9-29 使用内部bean定义避免错误的依赖注入引用
- ...
- <bean id="taskProxy" class="org.springframework.
aop.framework.ProxyFactoryBean">- <property name="target">
- <bean class="...MockTask"/>
- </property>
- <property name="proxyInterfaces">
- <list>
- <value>...ITask</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>performanceAdvisor</value>
- </list>
- </property>
- </bean>
因为autodetectInterfaces的默认值为true,如果确认目标对象所实现的接口就是要代理的接口,那么,完全可以省略通过interfaces或者proxyInterfaces明确指定代理接口的配置。代码清单9-29的配置内容可以精简如下:
- ...
- <bean id="taskProxy" class="org.springframework.
aop.framework.ProxyFactoryBean">- <property name="target">
- <bean class="...MockTask"/>
- </property>
- <property name="interceptorNames">
- <list>
- <value>performanceAdvisor</value>
- </list>
- </property>
- </bean>
如果没有指定要代理的接口类型,并且目标对象也没有实现任何接口,那么,ProxyFactory- Bean会采用基于类的代理方式为目标对象生成代理对象。不过,即使目标对象实现了某些接口,我们也可以强制ProxyFactoryBean采用基于类的代理方式来生成代理对象。与ProxyFactory一样,只要指定proxyTargetClass为true就可以了(见代码清单9-30)。
代码清单9-30 强制ProxyFactoryBean使用基于类的代理方式的配置示例
- ...
- <bean id="taskProxy" class="org.springframework.
aop.framework.ProxyFactoryBean">- <property name="target">
- <bean class="...MockTask"/>
- </property>
- <property name="proxyTargetClass">
- <value>true</value>
- </property>
- <property name="interceptorNames">
- <list>
- <value>performanceAdvisor</value>
- </list>
- </property>
- </bean>
不过,现在客户端代码不能将代理对象强制转型为ITask,而应该强制转型为目标对象的具体类型,即MockTask,如下所示:
- ApplicationContext ctx = ...;
- // ITask task = (ITask)ctx.getBean("taskProxy");
- // 错误!
- MockTask task = (MockTask)ctx.getBean("taskProxy");
- task.execute(null);
- ...
提示 有时,我们的应用可能需要依赖于第三方库,这些库中可能有些对象是出于简单实用的目的,就是没有进行面向接口编程,自然就没有实现任何接口。而且,我们自己设计和实现的类,可能出于某种目的,也是没有实现接口的必要,这时,就需要通过将proxyTargetClass设置为true来解决代理的问题。
说完了如何通过ProxyFactoryBean生成目标对象的代理对象(使用"基于接口的代理"方式也好,使用"基于类的代理"方式也好)。下面该说一下Introduction的代理了,因为它一直比较特立独行嘛!
为了演示Introduction的织入,我们引入一个ICounter接口定义以及一个简单实现类,然后将这个接口的行为和状态添加到ITask相应实现类中。ICounter接口以及相关实现类定义见代码清单9-31。
代码清单9-31 ICounter接口以及相关实现类定义
- public interface ICounter {
- void resetCounter();
- int getCounter();
- }
- public class CounterImpl implements ICounter {
- private int counter;
- public int getCounter() {
- counter++;
- return counter;
- }
- public void resetCounter() {
- counter = 0;
- }
- }
要将ICounter的行为添加到ITask相应实现类中,可以采用代码清单9-32所示的配置。
代码清单9-32 将ICounter行为添加到ITask的配置示例
- <bean id="task" class="...MockTask" singleton="false">
- </bean>
- <bean id="introducedTask" class="org.springframework.
aop.framework.ProxyFactoryBean" ?- singleton="false">
- <property name="targetName">
- <value>task</value>
- </property>
- <property name="proxyInterfaces">
- <list>
- <value>...ITask</value>
- <value>...ICounter</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>introductionInterceptor</value>
- </list>
- </property>
- </bean>
- <bean id="introductionInterceptor" ?
- class="org.springframework.aop.support.DelegatingIntroductionInterceptor"
- singleton="false">
- <constructor-arg>
- <bean class="...CounterImpl">
- </bean>
- </constructor-arg>
- </bean>
请注意,我们将目标对象的bean定义、ProxyFactoryBean的bean定义,以及相应Introduction- Interceptor的bean定义的scope,全部声明为prototype,也就是singleton="false",并且,这种情况下,我们使用的是"taskName"而不是"task"来指定目标对象(使用task通过ref指定prototype类型的依赖会有什么效果,在Spring的IoC容器部分已经讲述过了。)。这样才能保证每次取得的代理对象都持有各自独有的状态和行为,如下是调用执行的代码示例:
- ApplicationContext ctx = new ClassPathXmlApplicationContext("...");
- Object proxy1 = ctx.getBean("introducedTask");
- Object proxy2 = ctx.getBean("introducedTask");
- System.out.println(((ICounter)proxy1).getCounter());
- System.out.println(((ICounter)proxy1).getCounter());
- System.out.println(((ICounter)proxy2).getCounter());
因为proxy1和proxy2各自拥有独立的状态,所以,输出为:
- 1
- 2
- 1
我们之前说过,DelegatingIntroductionInterceptor是一个"伪军",如果不是采用prototype的scope为每一个代理对象都分配一个该类型实例,则无法保证各代理对象拥有各自的状态。不过,如果使用DelegatePerTargetObjectIntroductionInterceptor,那么可以共用一个该类型的Advice实例(即使用sigleton的scope),见代码清单9-33。
代码清单9-33 使用DelegatePerTargetObjectIntroductionInterceptor代替Delegating- IntroductionInterceptor后的配置实例
至于你的自定义IntroductionInterceptor,在应用的时候,请根据情况设置Introduction- Interceptor的scope以保证状态的独立性。有关ProxyFactoryBean的更多配置项细节,请参照对应的Javadoc,这里就不赘述了。我们得加快织入的速度了,毕竟,一个一个地配置ProxyFactoryBean可不是什么令人感到轻松、愉快的事情。
- <bean id="task" class="...MockTask" singleton="false">
- </bean>
- <bean id="introducedTask" class="org.springframework.aop.
framework.ProxyFactoryBean" ?- singleton="false">
- <property name="target"><ref bean="task"/></property>
- <property name="proxyInterfaces">
- <list>
- <value>...ITask</value>
- <value>...ICounter</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>introductionInterceptor</value>
- </list>
- </property>
- </bean>
- <bean id="introductionInterceptor" class="org.
springframework.aop.support. ?- DelegatePerTargetObjectIntroductionInterceptor">
- <constructor-arg index="0">
- <value>...CounterImpl</value>
- </constructor-arg>
- <constructor-arg index="1">
- <value>...ICounter</value>
- </constructor-arg>
- </bean>