在介绍增强时,我们可能注意到一个问题,增强被织入到目标类的所有方法中,假设我们希望有选择地织入到
目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了.
spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher
构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就
拥有了描述某些特定方法的能力.
ClassFilter只定义了一个方法matches(Class clazz),其参数代表一个被检测类,该方法判别被检测的类是否
匹配过滤条件.
spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器.所谓静态方法匹配器,它仅对方法名签名(包括
方法名和入参类型及顺序)进行匹配;而动态方法匹配器,会在运行时检查方法入参的值.表态匹配仅会判别一次;
而动态匹配因为每次调用方法的入参都可以不一样,所以每次调用方法都必须判断,因此,动态匹配对性能影响很
大.一般情况下,动态匹配不常用.方法匹配器的类型由isRuntime()返回值决定,返回false表示是静态方法匹配器
,返回true表示是动态方法匹配器.
-------------------------------------------------
切点类型
spring提供了6种类型切点:
静态方法切点: org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点的抽象
基类,默认情况下它匹配所有类.StaticMethodMathcerPointcut包括两个主要的子类,分别是
NameMatchMethodPointcut和AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名.而后者使用
正则表达式匹配方法签名.
动态方法切点: org.springframework.aop.support.DynamicMethodMatcherPointcut是动态方法切点的抽象
基类,默认情况下它匹配所有的类.DynamicMethodMatcherPointcut在spring 2.0下已经过时,可以使用
DefaultPointcutAdvisor和DynamicMethodMathcerPointcut动态方法匹配器替代之.
注解切点: org.springframework.aop.support.annotation.AnnotationMatchingPointcut实现类表示注解
切点,是spring 2.0新定义的切点.使用AnnotationMatchingPointcut支持在Bean中直接通过JDK5.0注解标签
定义的切点.
表达式切点: org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达
式语法而定义的接口.
流程切点: org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点.
ControlFlowPointcut是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某个方法直接或间接发起
调用.以此判断是否为匹配的连接点.
复合切点: org.springframework.aop.support.ComposablePointcut实现类是为创建多个切点而提供的方便操
作类.它所有的方法都返回ComposablePointcut类,这样,我们就可以使用链接表达式对切点进行操作.形如:
Pointcut pc = new composablePointcut().union(classFilter).intersection(methodMatcher)
.intersection(pointcut);
-------------------------------------------------
切面类型
由于增强既包含横切代码,又包含部分的连接点信息,所以我们可以仅通过增强类生成一个切面.但切点仅代表目标类
连接点的部分信息,所以仅有切点,我们无法制作出一个切面,必须结合增强才能制作出切面.spring使用
org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息.切面分为三类:
一般切面,切点切面,引介切面:
Advisor: 代表一般切面,它仅包含一个Advice.我们说过,Advice包含了横切代码和连接点的信息,所以Advice本
身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛了,所以一般
不会直接使用.
PointcutAdvisor: 代表具有切点的切面.它包含Advice和Pointcut两个类,这样,我们就可以通过类,方法名以及
方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面.
IntroductionAdvisor: 代表引介切面.引介切面是对应引介增强的特殊切面,它应用于类层面上,所以引介切点使用
ClassFilter进行定义.
---------
PointcutAdvisor主要有6个具体的实现类:
DefaultPointcutAdvisor: 最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的是引介
切面类型,一般可以通过扩展该类实现自定义的切面.
NameMatchMethodPointcutAdvisor: 通过该类可以定义按方法名定义切点的切面.
RegexpMethodPointcutAdvisor: 对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操
作.RegexpMethodPointcutAdvisor允许用户以正则表达式模式串定义方法匹配的切点.
StaticMethodMatcherPointcutAdvisor: 静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类.
AspectJExpressionPointcutAdvisor: 用于AspectJ切点表达式定义切点的切面,它是spring2.0新提供的类.
AspectJPointcutAdvisor: 用于AspectJ语法定义切点的切面,它也是spring2.0新提供的类.
-------------------------------------------------
静态普通方法名匹配切面
staticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,它通过StaticMethodMatcherPointcut定义切点,通过类
过滤和方法名匹配定义切点.如Waiter和Seller业务类:
package com.baobaotao.advisor;
public class Waiter{
public void greetTo(String name){
System.out.println("waiter greet to "+name+"...");
}
public void serverTo(String name){
System.out.println("waiter serving "+name+"...");
}
}
package com.baobaotao.advisor;
public class Seller{
public void greetTo(String name){
System.out.println("Seller greet to "+name+"...");
}
}
Seller拥有一个和Waiter相同名称的方法greetTo().现在,我们希望通过StaticMethodMatcherPointcutAdvisor定义一个切面,
在Waite#greetTo()方法调用前织入一个增强,即连接点为Waiter#greetTo()方法调用前的位置.具体的切面类的实现为:
package com.baobaotao.advisor;
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor{
//切点方法匹配规则
public boolean matches(Method method,Class clazz){
return "greetTo".equals(method.getName());
}
//
public ClassFilter getClassFilter(){
return new ClassFilter(){
public boolean matches(Class clazz){
return Waiter.class.isAssignableFrom(clazz);
}
}
}
}
因为StaticMethodMatcherPointcutAdvisor抽象类唯一需要定义的是matches()方法.在默认情况下,该切面匹配所有的类,这里
我们通过覆盖getClassFilter()方法,让它仅匹配Waiter类及其子类.
当然Advisor还需要一个增强类的配合,我们定义一个前置增强:
package com.baobaotao.advisor;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
public void before(Method method,Object[] args,Object obj)throws Throwable{
System.out.println(obj.getClass().getName()+"."+method.getName());
String clientname=(String)args[0];
System.out.println("How are you! Mr." + clientName+".");
}
}
下面我们使用spring配置来定义切面:
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter"/>
<bean id="sellerTarget" class="com.baobaotao.advisor.Seller"/>
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
<bean id="greetingAdvisor" class="com.baobaotao.advisor.GreetingAdvisor">
<property name="advice" ref="greetingAdvice"/><!-- 向切面注入一个前置增强 -->
</bean>
<bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptoNames">
<idref local="greetingAdvisor"/>
</property>
<property name="proxyTargetClass" value="true"/>
</bean>
<bean id="waiter" parent="parent">
<property name="target" ref="waiterTarget"/>
</bean>
<bean id="seller" parent="parent">
<property name="target" ref="sellerTarget"/>
</bean>
我们将greetAdvice增强装配到greetingAdvisor的切面中,StaticMethodMatcherPointcutAdvisor除了advice属性
外,还可以定义另外两个属性:
classFilter: 类匹配过滤器,在GreetingAdvisor中,我们用编码的方式设定了classFilter.
order: 切面织入时的顺序,该属性用于定义Ordered接口表示的顺序.
由于需要分别为waiter和Seller两个Bean定义代理器,两者有很多公共的配置信息,我们使用了一个父<bean>简化配置.
-------------------------------------------------
静态正则表达式方法匹配切面
在staticMethodMatcherPointcutAdvisor中,我们仅能通过方法名定义切点,这种描述方式不够灵活,假设目标类中有多
个方法,且它们都满足一定的命名规范,使用正则表达式进行匹配描述就要灵活多了.RegexpMethodPointcutAdvisor是正
则表达式方法匹配的切面实现类.
下面,我们直接使用RegexpMethodPointcutAdvisor,通过配置的方式为Waiter目标定义一个切面:
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns"> <!-- 用正则表达式定义目标类全限定方法名的匹配模式串 -->
<list>
<value>.*greet.*</value> <!-- 匹配模式串 -->
</list>
</property>
<property name="advice" ref="greetingAdvice"/>
</bean>
<bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<idref local="reqexpAdvisor"/>
</property>
<property name="proxyTargetClass" value="true"/>
<property name="target" ref="waiterTarget"/>
</bean>
RegexpMethodPointcutAdvisor除了patterns和advice两个属性外,还有另外两个属性,分别介绍:
pattern: 如果只有一个匹配模式串,可以使用该属性进行配置,patterns属性用于定义多个匹配模式串,这些匹配模式串之
间是"或"的关系.
order: 切面在织入时对应的顺序.
-------------------------------------------------
动态切面
我们可以使用DefaultPointcutAdvisor和DynamicMethodMathcerPointcut.
DynamicMethodMathcerPointcut是一个抽象类,它将isRuntime()标识为final且返回true,这样其子类就一定是一个动态的切点了.
该抽象类默认匹配所有的类和方法,因此需要通过扩展该类编写符合要求的动态切点:
package com.baobaotao.advisor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{
private static List<String> specialClientList = new ArrayList<String>();
static{
specialclientList.add("John");
specialclientList.add("Tom");
}
//对类进行静态切点检查
public ClassFilter getClassFilter(){
return new ClassFilter(){
public boolean matches(Class clazz){
System.out.println("调用getClassFilter()对"+clazz.getName()+"做静态检查");
return waiter.class.isAssignableFrom(clazz);
}
};
}
//对方法进行静态切点检查
public boolean matches(Method method,Class clazz){
System.out.println("调用matches(method,clazz)"+clazz.getName()+"."+method.getName()+"做静态检查");
return "greetTo".equals(method.getName());
}
//对方法进行动态切点检查
public boolean matches(Method method,Class clazz,Object[] args){
System.out.println("调用matches(method,clazz"+clazz.getName()+"."+method.getName()+"做动态检查");
String clientName=(String)args[0];
return specialClientList.contains(clientName);
}
}
GreetingDynamicPointcut类既有用于静态切点检查的方法,也有用于动态切点检查的方法.由于动态切点检查会对性能造成很大的影响,
我们应当尽量避免在运行时每次都对目标类的各个方法进行动态检查.spring采用这样的机制:在创建代理时对目标类的每个连接点使用
静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查了.如果静态切点检查是匹配的,在
运行时才进行动态切点检查.
在动态切点类定义静态切点检查的方法可以防止不必要的动态检查操作,因此极大地提高运行效率.
spring配置:
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter"/>
<bean id="dynamicAdvisor" class="org.springframework.aop.suport.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="com.baobaotao.advisor.GreetingDynamicPointctu"/> <1>
</property>
<property name="advice">
<bean class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
</property>
</bean>
<bean id="vaiter2" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<idref local="dynamicAdvisor"/>
</property>
<property name="proxyTargetClass" value="true"/>
<property name="target" ref="waiterTarget"/>
</bean>
动态切面的配置和静态切面的配置没有什么区别,我们使用DefaultPointcutAdvisor定义切面,在<1>处使用内部Bean方式注入动态切点
GreetingDynamicPointcut,增强依旧使用前面定义的GreetingBeforeAdvice.此外,DefaultPointcutAdvisor还有一个order属性,用于
定义切面的织入顺序.
-------------------------------------------------
流程切面
spring的流程切面由DefaultPointcutAdvisor和ControlFlowPointcut实现.流程切点代表由某个方法直接或间接发起调用的其他方法.
package com.baobaotao.advisor;
public class WaiterDelegate{
private Waiter waiter;
//waiter的方法通过该方法发起调用
public void service(String clientName){
waiter.greetTo(clientName);
waiter.serveTo(clientName);
}
public void setWaiter(Waiter waiter){
this.waiter = waiter;
}
}
如果我们希望所有由WaiterDelegate#service()方法发起调用的其他方法都织入GreetingBeforeAdvice增强,就必须使用流程切面来完成目标.
<bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="org.springframework.aop.support.ControlFlowPointcut">
<!-- 指定流程切点的类 -->
<constructor-arg type="java.lang.Class" vlaue="com.baobaotao.advisor.WaiterDelegate"/>
<!-- 指定流程切点的方法 -->
<constructor-arg type="java.lang.String" value="service"/>
</bean>
</property>
<property name="advice">
<bean class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
</property>
</bean>
<bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<idref local="controlFlowAdvisor"/>
</property>
<property name="proxyTargetClass" value="true"/>
<property name="target">
<bean class="com.baobaotao.advisor.Waiter"/>
</property>
</bean>
ControlFlowPointcut有两个构造函数,分别是ControlFlowPointcut(Class clazz)和ControlFlowPointcut(Class clazz,String methodName)
第一个构造函数指定一个类作为流程切点,而第二个构造函数指定一个类和某一个方法作为流程切点.
在这里,我们指定com.baobaotao.advisor.WaiterDelegate#service()方法作为流程切点,表示所有通过该方法直接或间接发起的调用匹配切点.
流程切面和动态切面从某种程度上说可以算是一类切面,因为两者都需要在运行期间判断动态的环境.和动态切面一样,流程切面对性能的影响也很大,
在JVM1.4上,流程切点通常比别的切点要慢5倍,在JVM1.3上要慢10倍.
-------------------------------------------------
复合切点切面
在前面的例子中,我们所定义的切面仅有一个切点,有时,一个切点可能难以描述目标连接点的信息.比如在前面流程切面的例子中,
假设我们希望由WaiterDelegate#service()发起调用且被调用的方法是Waiter#greetTo()时才织入增强.这个切点就是复合切
点,因为它由两个单独的切点共同确定,第一个切点是流程切点,另一个切点是方法名为"greetTo"的方法名切点.
spring为我们提供的ComposablePointcut把两个切点组合起来,通过切点的复合运算表示.ComposablePointcut可以将多个切点
以并集或交集的方式组合起来,提供了切点之间的复合运算功能.
ComposablePointcut本身也是一个切点,它实现了Pointcut的接口,下面先来了解一下ComposablePointcut的构造函数.
ComplsablePointcut(): 构造一个匹配所有类所有方法的复合切点.
ComposablePointcut(ClassFilter classFilter): 构造一个匹配特定类所有方法的复合切点.
ComposablePointcut(MethodMatcher methodMatcher): 构造一个匹配所有类特定方法的复合切点.
ComposablePointcut(ClassFilter classFilter,MethodMatcher methodMatcher): 构造一个匹配
特定类特定方法的复合切点.
---------
ComposablePointcut提供了三个交集运算的方法:
ComposablePointcut intersection(ClassFilter filter): 将复合切点和一个ClassFilter对象进
行交集运算,得到一个结果复合切点.
ComposablePointcut intersection(MethodMatcher mm): 将复合切点和一个MethodMatcher对象
进行交集运算,得到一个结果复合切点.
ComposablePointcut intersection(Pointcut other): 将复合切点和一个切点对象进行交集运算,
得到一个结果复合切点.
---------
ComposablePointcut提供了两个并集运算的方法:
ComposablePointcut union(ClassFilter filter): 将复合切点和一个ClassFilter对象进行并集
运算,得到一个结果复合切点.
ComposablePointcut union(MethodMatcher mm): 将复合切点和一个MethodMatcher对象进行并集
运算,得到一个结果复合切点.
ComposablePointcut没有提供直接对两个切点进行交并集运算的方法,如果需要对两个切点进行并交集
运算,可以使用spring提供的org.springframework.aop.support.Pointcuts工具类,该工具类中有两
个非常好用的静态方法:
Pointcut intersection(Pointcut a,Pointcut b): 对两个切点进行交集运算,返回一个结果切点,
该切点即是ComposablePointcut对象的实例.
Pointcut union(Pointcut a,Pointcut b): 对两个切点进行并集运算,返回一个结果切点,该切点
即是ComposablePointcut对象的实例.
下面,我们通过ComposablePointcut创建一个流程切点和方法名切点的相交切点,如例:
public class GreetingComposablePointcut{
public Pointcut getIntersectionPointcut(){
//创建一个复合切点
ComposablePointcut cp = new ComposablePointcut();
//创建一个流程切点
Pointcut pt1 = new ControlFlowPointcut(WaiterDelegate.class,"service");
//创建一个方法名切点
NameMatchMethodPointcut pt2 = new NameMatchMethodPointcut();
pt2.addMethodName("greetTo");
//两个切点进行交集操作.
return cp.intersection(pt1).intersection(pt2);
}
}
通过GreetingComposablePointcut#getIntersectionPointcut()方法,我们即可得到一个相交的复合切点.
配置复合切点的切面和配置其他切点一样:
<bean id="gcp" class="com.baobaotao.advisor.GreetingComposablePointcut"/>
<bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<!-- 引用gcp.getIntersectionPointcut()所返回的复合切点 -->
<util:property-path path="gcp.intersectionPointctu"/>
</property>
<property name="advice">
<bean class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
</property>
</bean>
<bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<!-- 使用复合切面 -->
<idref local="composableAdvisor"/>
</property>
<property name="proxyTargetClass" value="true"/>
<property name="target">
<bean class="com.baobaotao.advisor.Waiter"/>
</property>
</bean>
-------------------------------------------------
引介切面
引介切面使用IntroductionAdvisor.它有两个实现类,分别是:DefaultIntroductionAdvisor和DeclareParentsAdvisor,
前者是引介切面最常用的实现类,而后者是spring2.0新添加的实现类,它用于实现使用AspectJ语言的DeclareParent注解
表示的引介切面.
DefaultIntroductionAdvisor拥有三个构造函数:
DefaultIntroductionAdvisor(Advice advice): 通过一个增强创建的引介切面,引介切面将为目标对象新增增强对象中所有接口的实现.
DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice,Class clazz): 通过一个增强和一个指定的接口类创建
引介切面,仅为目标对象新增clazz接口的实现.
DefaultIntroductionAdvisor(advice advice,IntroductionInfo introductionInfo): 通过一个增强和一个IntroductionInfo
创建一个引介切面,目标对象需要实现哪些接口.由IntroductionInfo对象的getInterfaces()表示.
下面,我们通过DefaultIntroductionAdvisor为上面的引介增强配置切面,我们会发现这种方式经前面的方式要更简洁,更清晰.
<bean id="introduceAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg>
<bean class="com.baobaotao.Introduce.ComtrollablePerformanceMonitor"/>
</constructor-arg>
</bean>
<bean id="forumServiceTarget" class="com.baobaotao.introduce.ForumService"/>
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<idref local="introduceAdvisor"/>
</property>
<property name='target" ref="forumServiceTarget"/>
<property name="proxyTargetClass" value="true"/>
</bean>