Spring AOP 创建切面
增强被织入到目标类的所有方法中,但是如果需要有选择性的织入到目标类某些特定的方法中时,就需要使用切点进行目标连接点的定位。增强提供了连接点方位信息:如织入到方法前面、后面等,而切点进一步描述织入到哪些类的哪些方法上。Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到特定方法上。这样Pointcut就拥有了描述某些类的某些特定方法的能力。
Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。静态方法匹配器,它仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;而动态方法匹配器,会在运行期检查方法入参的值。静态匹配仅会判别一次;而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断。因此,动态匹配对性能的影响很大。
1、切点类型
1)静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcut和AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。
2)动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut 是动态方法切点的抽象基类,默认情况下它匹配所有的类。DynamicMethodMatcherPointcut类已经过时,可以使用DefaultPointcutAdvisor 和DynamicMethodMatcherPointcut动态方法匹配器替代之。
3)注解切点:org.springframework.aop.support.AnnotationMatchingPointcut实现类表示注解切点。使用AnnotationMatchingPointcut支持在Bean中直接通过JDK5.0注解标签定义的切点。
4)表达式切点:org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式语法而定义的接口。
5)流程切点:org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点。ControlFlowPointcut是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点。
6)复合切点:org.springframework.aop.suppot.ComposablePointcut实现类是为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类,这样,我们就可以使用连接表达式对切点进行操作。
2、切面类型
Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面可以分为三类:一般切面、切点切面和引介切面。
1)Advisor:代表一般切面,它仅包含一个Advice。由于Advice包含了横切代码和连接点的信息,所以Advice本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用。
2)PointcutAdvisor:代表具有切点的切面,它包含Advice和Pointcut两个类,这样,我们就可以通过类、方法名以及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。
3)IntroductionAdvisor:代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用ClassFilter进行定义。
PointcutAdvisor主要有6个具体的实现类:
1)DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的是引介的切面类型,一般可以通过扩展该类实现自定义的切面。
2)NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面。
3)RegexpMethodPointcutAdvisor:允许用户以正则表达式模式串定义方法匹配的切点。
4)StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类。
5)AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面,它是Spring 2.0 新提供的类。
6)AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面,它也是Spring 2.0 新提供的类。
3、静态普通方法名匹配切面
StaticMethodMatcherPointcutAdvisor 代表一个静态方法匹配切面,它通过 StaticMethodMatcherPointcut 定义切点,通过类过滤器和方法名匹配定义切点。
Waiter业务类:
package com.yyq.aop; 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 + "."); } }
Seller业务类:
package com.yyq.aop; public class Seller { public void greetTo(String name) { System.out.println("seller greet to " + name + "."); } }
GreetingAdvisor切面实现类:
package com.yyq.aop; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; import java.lang.reflect.Method; public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor { @Override public boolean matches(Method method, Class<?> aClass) { return "greetTo".equals(method.getName()); } public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> aClass) { return Waiter.class.isAssignableFrom(aClass); } }; } }
GreetingBeforeAdvice前置增强实现类:
package com.yyq.aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class GreetingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(o.getClass().getName() + "." + method.getName()); String clientName = (String) objects[0]; System.out.println("Hi! Mr." + clientName + "."); } }
配置切面:静态方法配置切面
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="waiterTarget" class="com.yyq.aop.Waiter"/> <bean id="sellerTarget" class="com.yyq.aop.Seller"/> <bean id="greetingAdvice" class="com.yyq.aop.GreetingBeforeAdvice"/> <bean id="greetingAdvisor" class="com.yyq.aop.GreetingAdvisor" p:advice-ref="greetingAdvice"/> <bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true"/> <bean id="waiter" parent="parent" p:target-ref="waiterTarget"/> <bean id="seller" parent="parent" p:target-ref="sellerTarget"/> </beans>
测试方法:
@Test public void testAdvisor(){ String configPath = "com\\yyq\\aop\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter"); Seller seller = (Seller)ctx.getBean("seller"); waiter.greetTo("John"); waiter.serveTo("John"); seller.greetTo("John"); }
结果输出:
com.yyq.aop.Waiter.greetTo
Hi! Mr.John.
waiter greet to John.
waiter serving to John.
seller greet to John.
4、静态正则表达式方法匹配切面
使用正则表达式进行匹配描述能够灵活匹配目标方法。
通过正则表达式定义切面:
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:advice-ref="greetingAdvice"> <property name="patterns"> <list> <value>.*greet.*</value> </list> </property> </bean> <bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="regexpAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/>
测试方法:
@Test public void testAdvisor2(){ String configPath = "com\\yyq\\aop\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter1"); waiter.greetTo("John"); waiter.serveTo("John"); }
输出结果:
com.yyq.aop.Waiter.greetTo
Hi! Mr.John.
waiter greet to John.
waiter serving to John.
5、动态切面
DynamicMethodMatcherPointcut是一个抽象类,它将isRuntime()标识为final且返回true,这样其子类就一定是一个动态的切点了,该抽象类默认匹配所有的类和方法,因此需要通过扩展该类编写符合要求的顶贴切点。
GreetingDynamicPointcut动态切面实现类:
package com.yyq.aop; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; 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() { @Override public boolean matches(Class<?> aClass) { System.out.println("调用getClassFilter()对" + aClass.getName() + "做静态检查。"); return Waiter.class.isAssignableFrom(aClass); } }; } public boolean matches(Method method, Class clazz) { System.out.println("调用matches(method,clazz)" + clazz.getName() + "." + method.getName() + "做静态检查。"); return "greetTo".equals(method.getName()); } @Override public boolean matches(Method method, Class<?> aClass, Object[] objects) { System.out.println("调用matches(method,aClass)" + aClass.getName() + "." + method.getName() + "做动态检查。"); String clientName = (String)objects[0]; return specialClientList.contains(clientName); } }
Spring动态检查机制:在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查了;如果静态切点检查时匹配的,在运行时才进行动态切点检查。
动态切面配置:
<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <bean class="com.yyq.aop.GreetingDynamicPointcut"/> </property> <property name="advice"> <bean class="com.yyq.aop.GreetingBeforeAdvice"/> </property> </bean> <bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="dynamicAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/>
测试方法:
@Test public void testAdvisor3(){ String configPath = "com\\yyq\\aop\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter2"); waiter.serveTo("Peter"); waiter.greetTo("Peter"); waiter.serveTo("John"); waiter.greetTo("John"); }
输出结果:
调用getClassFilter()对com.yyq.aop.Waiter做静态检查。
调用matches(method,clazz)com.yyq.aop.Waiter.greetTo做静态检查。
调用getClassFilter()对com.yyq.aop.Waiter做静态检查。
调用matches(method,clazz)com.yyq.aop.Waiter.serveTo做静态检查。
调用getClassFilter()对com.yyq.aop.Waiter做静态检查。
调用matches(method,clazz)com.yyq.aop.Waiter.toString做静态检查。
调用getClassFilter()对com.yyq.aop.Waiter做静态检查。
调用matches(method,clazz)com.yyq.aop.Waiter.clone做静态检查。
调用getClassFilter()对com.yyq.aop.Waiter做静态检查。
调用matches(method,clazz)com.yyq.aop.Waiter.serveTo做静态检查。
waiter serving to Peter.
调用getClassFilter()对com.yyq.aop.Waiter做静态检查。
调用matches(method,clazz)com.yyq.aop.Waiter.greetTo做静态检查。
调用matches(method,aClass)com.yyq.aop.Waiter.greetTo做动态检查。
waiter greet to Peter.
waiter serving to John.
调用matches(method,aClass)com.yyq.aop.Waiter.greetTo做动态检查。
com.yyq.aop.Waiter.greetTo
Hi! Mr.John.
waiter greet to John.
6、流程切面
Spring的流程切面由DefaultPointcutAdvisor 和ControlFlowPointcut实现。流程切点代表由某个方法直接或间接发起调用的其他方法。
WaiterDelegate类代理Waiter所有的方法:
package com.yyq.aop; 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; } }
配置控制流程切面:
<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg type="java.lang.Class" value="com.yyq.aop.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="greetingAdvice"/> <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="controlFlowAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/>
测试方法:
@Test public void testAdvisor4(){ String configPath = "com\\yyq\\aop\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter3"); WaiterDelegate wd = new WaiterDelegate(); wd.setWaiter(waiter); waiter.serveTo("Peter"); waiter.greetTo("Peter"); wd.service("Peter"); }
输出结果:
waiter serving to Peter.
waiter greet to Peter.
com.yyq.aop.Waiter.greetTo
Hi! Mr.Peter.
waiter greet to Peter.
com.yyq.aop.Waiter.serveTo
Hi! Mr.Peter.
waiter serving to Peter.
7、复合切点切面
假设我们希望由WaiterDelegate#service()发起调用且被调用的方法是Waiter#greetTo()时才织入增,。这个切点就是复合切点,因为它是由两个单独的切点共同确定。ComposablePointcut 可以将多个切点以并集或交集的方式组合起来,提供了切点之间复合运算的功能。
GreetingComposablePointcut复合切点实现类:
package com.yyq.aop; import org.springframework.aop.Pointcut; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.ControlFlowPointcut; import org.springframework.aop.support.NameMatchMethodPointcut; 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((Pointcut)pt2); } }
配置复合切点切面:
<bean id="gcp" class="com.yyq.aop.GreetingComposablePointcut"/> <bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:pointcut="#{gcp.intersectionPointcut}" p:advice-ref="greetingAdvice"/> <bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="composableAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/>
测试方法:
@Test public void testAdvisor5(){ String configPath = "com\\yyq\\aop\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter4"); WaiterDelegate wd = new WaiterDelegate(); wd.setWaiter(waiter); waiter.serveTo("Peter"); waiter.greetTo("Peter"); wd.service("Peter"); }
输出结果:
waiter serving to Peter.
waiter greet to Peter.
com.yyq.aop.Waiter.greetTo
Hi! Mr.Peter.
waiter greet to Peter.
waiter serving to Peter.
8、引介切面
引介切面是引介增强的封装器,通过引介切面,我们更容易为现有对象添加任何接口的实现。
配置引介切面:
<bean id="introduceAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"> <constructor-arg> <bean class="com.yyq.advice.ControllablePerformanceMonitor" /> </constructor-arg> </bean> <bean id="forumServiceTarget" class="com.yyq.advice.ForumService" /> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="introduceAdvisor" p:target-ref="forumServiceTarget" p:proxyTargetClass="true"/>