在之前说增强的时候,有一个问题那就是,增强被织入了目标类的所有方法中,加入我们希望有选择的织入到目标类的特定方法中,就需要使用切点进行目标连接点的定位了,增强提供了连接点方位信息:如织入到方法前还是方法后,而切点进一步描述织入到哪些类的哪些方法上。
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.lee
waiter greet to lee...
waiter serving to bin...
说明:我们定义的GreetingAdvisor包含了两个判定,ClassFilter和MethodMatcher这样我们就只定位到了Waiter的greetTo方法;
当然我们也可以通过spring配置的方式,配置如下:
<!-- 配置切面:静态方法匹配切面 --> <bean id="waiterTarget" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter" /> <bean id="sellerTarget" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Seller" /> <bean id="greetingBeforeAdvice" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.GreetingBeforeAdvice" /> <bean id="greetingAdvisor" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.GreetingAdvisor" p:advice-ref="greetingBeforeAdvice" /> <bean id="greetingParent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" /> <bean id="getWaiter" parent="greetingParent" p:target-ref="waiterTarget" /> <bean id="getSeller" parent="greetingParent" p:target-ref="sellerTarget" />
有时候我们可能需要对满足一定命名规范的方法进行织入增强,这个时候我们可以使用正则表达式方法匹配切面,配置如下:
<!-- 配置切面:静态方法匹配切面 --> <bean id="waiterTarget" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter" /> <bean id="sellerTarget" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Seller" /> <bean id="greetingBeforeAdvice" class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.GreetingBeforeAdvice" /> <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:advice-ref="greetingBeforeAdvice"> <property name="patterns"> <list> <value>.*greet.*</value> </list> </property> </bean> <bean id="greetingParent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="regexpAdvisor" p:proxyTargetClass="true" /> <bean id="getWaiter" parent="greetingParent" p:target-ref="waiterTarget" /> <bean id="getSeller" parent="greetingParent" p:target-ref="sellerTarget" />
以上是通过方法名来匹配切面,下面我们看动态切面;
二、动态切面
在低版本中,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 List<String> specialClientList = new ArrayList<String>(); 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); } }
<!-- 动态切面配置 --> <bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <bean class="spring.aop.DynamicMethodMatcherPointcutDemo.GreetingDynamicPointcut" /> </property> <property name="advice"> <bean class="spring.aop.StaticMethodMatcherPointcutAdvisorDemo.GreetingBeforeAdvice" /> </property> </bean> <bean id="getDynamicWaiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="dynamicAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true" />
三、流程切面
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增强,那么我们就必须使用流程切面来完成:
<!-- 流程控制切面配置 --> <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut"> <!-- 指定流程切点的类 --> <constructor-arg type="java.lang.Class" value="spring.aop.ControlFlowPointcutDemo.WaiterDelegate" /> <!-- 指定流程切点的方法 --> <constructor-arg type="java.lang.String" value="service" /> </bean> <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:pointcut-ref="controlFlowPointcut" p:advice-ref="greetingBeforeAdvice" /> <bean id="getControlWaiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="controlFlowAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true" />
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.lee
waiter greet to lee...
输出切点:spring.aop.StaticMethodMatcherPointcutAdvisorDemo.Waiter.serveTo
how are you! Mr.lee
waiter serving to lee...
总结:流程切面和动态切面从某种程度上来说是一类切面,都需要在运行期间判断动态的环境,开销很大,JVM1.4上,流程切面比别的切面要慢5倍!