在之前说增强的时候,有一个问题那就是,增强被织入了目标类的所有方法中,加入我们希望有选择的织入到目标类的特定方法中,就需要使用切点进行目标连接点的定位了,增强提供了连接点方位信息:如织入到方法前还是方法后,而切点进一步描述织入到哪些类的哪些方法上。
Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它功过ClassFliter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就有了描述某些类的某些特定方法的能力。
Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器:
1.所谓静态方法匹配器,它仅对方法签名(包括方法名和入参类型、顺序)进行匹配;
2.动态匹配器,会在运行期检查方法入参的值。
静态匹配仅会判别一次;而动态匹配因为每次调用方法的入参可能都不一样,所以每次调用方法都会判断,因此动态匹配对性能的影响很大,一般情况下,动态匹配不常用。方法匹配器的类型由isRuntime()返回值决定,返回false表示静态方法匹配器,反之则是动态方法匹配器;
一、静态方法匹配器(静态普通方法名匹配切面)
1.两个业务类
package spring.aop.StaticMethodMatcherPointcutAdvisorDemo;import org.springframework.aop.framework.ProxyFactory;public class Waiter { public void greetTo(String name) { System.out.println("waiter greet to " + name + "..."); } public void serveTo(String name) { System.out.println("waiter serving to " + name + "..."); } public static void main(String[] args) { Waiter waiter = new Waiter(); GreetingAdvisor grettingAdvisor = new GreetingAdvisor(); GreetingBeforeAdvice greetingBeforeAdvice = new GreetingBeforeAdvice(); ProxyFactory pf = new ProxyFactory(); pf.setOptimize(true); grettingAdvisor.setAdvice(greetingBeforeAdvice); pf.addAdvisor(grettingAdvisor); pf.setTarget(waiter); //pf.addAdvice(greetingBeforeAdvice); Waiter waiterProxy = (Waiter)pf.getProxy(); waiterProxy.greetTo("lee"); waiterProxy.serveTo("bin"); }}
package spring.aop.StaticMethodMatcherPointcutAdvisorDemo;public class Seller { public void greetTo(String name) { System.out.println("seller greet to " + name + "..."); }}
2.定义一个切面,在Waiter#greetTo()方法调用前织入一个增强
package spring.aop.StaticMethodMatcherPointcutAdvisorDemo;import java.lang.reflect.Method;import org.springframework.aop.ClassFilter;import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor { /** * */ private static final long serialVersionUID = -3558599577522442437L; @Override public boolean matches(Method arg0, Class arg1) { // 切点方法匹配规则:方法名必须为greetTo return "greetTo".equals(arg0.getName()); } // 切点类匹配规则:Waiter的类或者子类 @Override public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class arg0) { return Waiter.class.isAssignableFrom(arg0); } }; }}
3.增强类的配合,定义一个前置增强
package spring.aop.StaticMethodMatcherPointcutAdvisorDemo;import java.lang.reflect.Method;import org.springframework.aop.MethodBeforeAdvice;public class GreetingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] aobj, Object obj) throws Throwable { System.out.println("输出切点:" + obj.getClass().getName() + "." + method.getName()); String clientName = (String) aobj[0]; System.out.println("how are you! Mr." + clientName); }}
如果我们直接运行Waiter.java里的main方法可以得到如下输出:
输出切点:spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter.greetTo
how are you! Mr.leewaiter greet to lee...waiter serving to bin...说明:我们定义的GreetingAdvisor包含了两个判定,ClassFilter和MethodMatcher这样我们就只定位到了Waiter的greetTo方法;
当然我们也可以通过spring配置的方式,配置如下:
有时候我们可能需要对满足一定命名规范的方法进行织入增强,这个时候我们可以使用正则表达式方法匹配切面,配置如下:
.*greet.*
以上是通过方法名来匹配切面,下面我们看动态切面;
二、动态切面
在低版本中,Spring提供了用于创建动态切面的DynamicMethodMatcherPointcutAdvisor抽象类,因为该类在功能上和其他类有重叠,会给开发者造成选择上的困惑,因此在Spring2.0中已经过期,我们可以使用DefaultPointcutAdvisor和DynamicMethodMatcherPointcut来完成相同的功能。DynamicMethodMatcherPointcut是一个抽象类,它将isRuntime()标示成final并返回true,这样其子类就是一个动态的切点了,该抽象类默认匹配所有类和方法,因此需要通过扩展该类编写符合需求的动态切点。
package spring.aop.DynamicMethodMatcherPointcutDemo;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;import org.springframework.aop.ClassFilter;import org.springframework.aop.support.DynamicMethodMatcherPointcut;import spring.aop.beforeadvicedemo.Waiter;public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut { private static ListspecialClientList = new ArrayList (); static { specialClientList.add("nicholas"); specialClientList.add("lee"); } // 对类进行静态切点检查 public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class arg0) { System.out.println("调用getClassFilter()对" + arg0.getName() + "做静态检查"); return Waiter.class.isAssignableFrom(arg0); } }; } @Override public boolean matches(Method arg0, Class arg1) { System.out.println("调用matches()" + arg1.getName() + "." + arg0.getName() + "做静态检查"); return "greetTo".equals(arg0.getName()); } @Override public boolean matches(Method arg0, Class arg1, Object[] arg2) { System.out.println("调用matches()" + arg1.getName() + "." + arg0.getName() + "做动态检查"); String clientName = (String) arg2[0]; return specialClientList.contains(clientName); }}
三、流程切面
Spring的流程切面由DefaultPointcutAdvisor和ControlFlowPointcut实现。流程切点代表由某个方法直接或间接发起调用的其他方法,看一个实例,我们通过一个WaiterDelegate类代理Waiter所有的方法:
1.WaiterDelegate代理类
package spring.aop.ControlFlowPointcutDemo;import spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter;public class WaiterDelegate { private Waiter waiter; public void service(String clientName){ waiter.greetTo(clientName); waiter.serveTo(clientName); } public void setWaiter(Waiter waiter) { this.waiter = waiter; }}
如果我们希望由代理类WaiterDelegate#service()方法发起调用的其他方法都织入GreetingBeforeAdvice增强,那么我们就必须使用流程切面来完成:
package spring.aop.ControlFlowPointcutDemo;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter;public class test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml"); Waiter waiter = (Waiter) ctx.getBean("getControlWaiter"); WaiterDelegate wd = new WaiterDelegate(); wd.setWaiter(waiter); wd.service("lee"); //waiter.serveTo("lee"); }}
输出:
输出切点:spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter.greetTo
how are you! Mr.leewaiter greet to lee...输出切点:spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter.serveTo
how are you! Mr.leewaiter serving to lee...总结:流程切面和动态切面从某种程度上来说是一类切面,都需要在运行期间判断动态的环境,开销很大,JVM1.4上,流程切面比别的切面要慢5倍!