增強被織入到目標類的所有方法中,但是如果需要有選擇性的織入到目標類某些特定的方法中時,就需要使用切點進行目標連接點的定位。增強提供了連接點方位信息:如織入到方法前面、后面等,而切點進一步描述織入到哪些類的哪些方法上。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"/>