aop是面向切面編程(aspect oriented programing)的簡稱。aop的出現並非要徹底替代oop,僅是做爲oop的有益補充。
aop的應用場合是有限的,通常只適合於那些具備橫切邏輯的應用場合。java
性能監測正則表達式
訪問控制spring
事務管理數據庫
日誌記錄
...編程
一個類或一段程序代碼擁有一些具備邊界
性質的特定點,這些代碼中的特定點就稱爲鏈接點
。好比,app
類開始初始化前,後ide
類中某個方法調用前,後oop
方法拋出異常後
...性能
鏈接點由兩個信息肯定:測試
用方法表示的程序執行點
用相對點表示的方位
如在Test.foo()方法執行前的鏈接點,執行點爲Test.foo(),方位爲該方法執行前的位置。
spring使用切點對執行點定位,而方位則在加強類型中定義.
每一個程序類均可能有多個鏈接點,aop經過切點
定位特定點。類比於數據庫查詢:鏈接點至關於數據庫中的記錄,切點至關於查詢條件。
切點和鏈接點不是一對一關係,一個切點能夠匹配多個鏈接點。
切點只定位到某個方法上,若是但願定位到具體的鏈接點上,還須要提供方位信息。
加強是織入到目標類鏈接點上的一段代碼.它除用於描述一段代碼外,還擁有另外一個和鏈接點相關的信息,這即是執行點的方位
。結合執行點方位信息和切點信息,就能夠找到特定的鏈接點了。
spring提供的加強接口都是帶方位名的
:BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等。
加強邏輯的織入目標類。
引介是一種特殊的加強,它爲類添加一些屬性和方法。這樣,即便一個業務類原來沒有實現某個接口,經過引介,也能夠動態的爲業務類添加接口的實現邏輯,讓業務類成爲這個接口的實現類。
織入是將加強添加對目標類具體鏈接點的過程。aop有三種織入方式:
編譯期織入,這要求使用特殊的java編譯器
類裝載期織入,這要求使用特殊的類裝載器
動態代理織入,在運行期爲目標類添加加強,生成子類
spring使用第3種方式織入,aspectj使用第1,2種方式。
一個類被aop織入加強後,就產生一個結果類,它融合了原來類和加強邏輯的代理類。咱們能夠採用調用原來類相同的方式調用代理類。
切面由切點和加強(引介)組成,它既包括了橫切邏輯的定義,也包括了鏈接點的定義。spring aop負責實施切面,它將切面所定義的橫切邏輯織入到切面所指定的鏈接點鐘。
場景:高級餐廳的服務員在回答顧客以前都會說'你好!...'.
public class Waiter { public void check(String name){ System.out.println("結帳?"+name); } public void serve(String name){ System.out.println("要點什麼?"+name); } }
前置加強
import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetAdvice implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] args, Object obj)throws Throwable { String clientName=args[0].toString(); System.out.println("你好!"+clientName); } }
測試
import org.springframework.aop.BeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class TestBeforeAdvice { public static void main(String[] args){ Waiter target=new Waiter(); BeforeAdvice advice=new GreetAdvice(); ProxyFactory pf=new ProxyFactory();//spring提供的代理工廠 pf.setTarget(target);//設置代理目標 pf.addAdvice(advice);//添加加強 Waiter proxy=(Waiter)pf.getProxy();//代理實例 proxy.serve("TheViper"); proxy.check("TheViper"); } }
結果
你好!TheViper 來點什麼?TheViper 你好!TheViper 結帳?TheViper
ProxyFactory
內部使用JDK代理或CGLib代理,將加強應用到目標類。
還能夠將接口設置爲代理目標。
... ProxyFactory pf=new ProxyFactory(); pf.setInterfaces(target,getClass().getInterfaces); pf.setTarget(target); ...
application-context.xml
<?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.xsd "> <bean id='greetAdvice' class='com.GreetAdvice'></bean> <bean id='target' class='com.Waiter'></bean> <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' p:target-ref='target' p:interceptorNames='greetAdvice'/> </beans>
測試
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestBeforeAdvice { public static void main(String[] args){ ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml"); Waiter waiter=(Waiter)ctx.getBean("waiter"); waiter.serve("TheViper"); waiter.check("TheViper"); } }
ProxyFactoryBean
經常使用配置:
target:代理的目標對象
proxyInterfaces:代理所要實現的接口,能夠是多個接口。該屬性還有一個別名屬性interfaces
interceptorNames:須要植入目標對象的Bean列表。這些Bean必須是實現了org.aopalliance.intercept.MethodInterceptor
或org.springframework.aop.Advisor
的Bean,配置中的順序對應調用的順序。
singleton:返回的代理是否爲單例,默認爲單例
optimize:設置爲true時,強制使用CGLib代理。對於singleton代理,推薦使用CGLib,對於其餘做用域類型的代理,最好使用JDK代理。由於CGLib建立代理速度慢,而建立出的代理對象運行效率較高。JDK代理的表現與之相反
proxyTargetClass:是否對類進行代理(不是對接口進行代理),設置爲true時,使用CGLib
從上面spring配置能夠看到,ProxyFactoryBean
用的是JDK代理,若是將proxyTargetClass設置爲true後,無需再設置proxyInterfaces屬性,即便設置了也會被忽略。
場景:服務員和顧客交流後,禮貌的說'please enjoy yourself'.
後置加強
import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class GreetAfterAdvice implements AfterReturningAdvice{ @Override public void afterReturning(Object returnObj,Method method,Object[] args,Object obj)throws Throwable { //returnObj:目標實例方法返回的結果 method:目標類的方法 args:目標實例的方法參數 obj:目標類實例 System.out.println("please enjoy yourself!"); } }
spring配置
... <bean id='greetBeforeAdvice' class='com.GreetAdvice'></bean> <bean id='greetAfterAdvice' class='com.GreetAfterAdvice'></bean> <bean id='target' class='com.Waiter'></bean> <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' p:target-ref='target' p:interceptorNames='greetBeforeAdvice'/> ...
結果
你好!TheViper 要點什麼?TheViper please enjoy yourself! 你好!TheViper 結帳?TheViper please enjoy yourself!
環繞加強容許在目標類方法調用先後織入橫切邏輯,它綜合實現了前置,後置加強兩種的功能。
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class GreetingInterceptor implements MethodInterceptor{ @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] args=invocation.getArguments(); String clientName=args[0].toString(); System.out.println("你好!"+clientName); Object obj=invocation.proceed();//經過反射調用目標方法 System.out.println("please enjoy yourself!"); return obj; } }
... <bean id='greetingInterceptor' class='com.GreetingInterceptor'></bean> <bean id='target' class='com.Waiter'></bean> <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' p:target-ref='target' p:interceptorNames='greetingInterceptor,greetAfterAdvice'/> ...
異常拋出加強最適合的場景是事務管理。
import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class TransactionManager implements ThrowsAdvice{ public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{ System.out.println("method:"+method.getName()); System.out.println("拋出異常"+ex.getMessage()); System.out.println("回滾"); } }
引介加強不是在目標方法周圍織入加強,而是爲目標類建立新的方法和屬性
。因此引介加強的鏈接點是類級別的,非方法級別.
前面織入加強時,都是織入到目標類的全部方法中。這節將會介紹如何讓加強提供鏈接點方位的信息,如織入到方法前面,後面等,而切點進一步描述具體織入哪些類的哪些方法上。
spring經過org.springframework.aop.Pointcut接口
描述切點,Pointcut
由ClassFilter
和MethodMatcher
構成。
經過ClassFilter
定位到某些特定類上,經過MethodMatcher
定位到某些特定方法上。
此外,spring還提供註解切點和表達式切點,二者都使用AspectJ的切點表達式語言。
靜態方法切點
動態方法切點
註解切點
表達式切點
流程切點
複合切點
StaticMethodMatcherPointcutAdvisor
表明一個靜態方法匹配切面,它經過StaticMethodMatcherPointcut
定義切點,經過類過濾和方法名匹配定義切點。
例子,Seller類也有serve方法
public class Seller { public void serve(String name){ System.out.println("seller說:要點什麼?"+name); } }
前置加強(advice)
public class GreetingBeforeAdvice implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] args, Object obj)throws Throwable { String clientName=args[0].toString(); System.out.println("你好!"+clientName); } }
切面(advisor)
import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; public class GreetAdvisor extends StaticMethodMatcherPointcutAdvisor{ @Override public boolean matches(Method method, Class<?> cls) {//切點方法匹配 return "serve".equals(method.getName()); } //切點類匹配規則:Waiter的類或子類 public ClassFilter getClassFilter(){ return new ClassFilter(){ public boolean matches(Class cls){ return Waiter.class.isAssignableFrom(cls); } }; } }
spring配置
... <bean id='greetBeforeAdvice' class='com.GreetingBeforeAdvice'></bean> <bean id='waiterTarget' class='com.Waiter'></bean> <bean id='sellerTarget' class='com.Seller'></bean> <!-- 向切面注入前置加強 --> <bean id='greetingAdvisor' class='com.GreetAdvisor' p:advice-ref='greetBeforeAdvice'></bean> <bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='greetingAdvisor'/> <bean id='waiter' parent='parent' p:target-ref='waiterTarget'></bean><!-- waiter代理 --> <bean id='seller' parent='parent' p:target-ref='sellerTarget'></bean><!-- seller代理 --> ...
<bean id='parent' abstract='true' ...>
表示經過一個父<bean>定義公共的配置信息。
測試
ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml"); Waiter waiter=(Waiter)ctx.getBean("waiter"); Seller seller=(Seller)ctx.getBean("seller"); waiter.serve("TheViper"); waiter.check("TheViper"); seller.serve("TheViper");
結果
你好!TheViper waiter說:要點什麼?TheViper waiter說:結帳?TheViper seller說:要點什麼?TheViper
能夠看到切面只是織入到Waiter.serve()方法調用前的鏈接點上,而Waiter.check()和Seller.serve()都沒有織入切面。
RegexpMethodPointcutAdvisor
是正則表達式方法匹配的切面實現類。該類已是功能齊備的的實現類了,通常狀況下,無需擴展該類。
... <bean id='regexpAdvisor' class='org.springframework.aop.support.RegexpMethodPointcutAdvisor' p:advice-ref='greetBeforeAdvice'> <property name="patterns"> <list> <value>.*che.*</value> </list> </property> </bean> <bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='regexpAdvisor'/> ...
這裏定義了一個匹配模式.*che.*
,它會匹配check()方法。
... waiter.serve("TheViper"); waiter.check("TheViper"); ...
waiter說:要點什麼?TheViper 你好!TheViper waiter說:結帳?TheViper
import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{ private static List<String> specialCients=new ArrayList<String>(); static{ specialCients.add("Tom");//添加白名單 specialCients.add("TheViper"); } //切點類匹配規則:Waiter的類或子類 public ClassFilter getClassFilter(){//靜態匹配 return new ClassFilter(){ public boolean matches(Class cls){ System.out.println("對"+cls.getName()+"類作靜態檢查"); return Waiter.class.isAssignableFrom(cls); } }; } public boolean matches(Method method, Class<?> cls) {//切點方法靜態匹配 System.out.println("對"+cls.getName()+"類的"+method.getName()+"方法作靜態檢查"); return "serve".equals(method.getName()); } @Override public boolean matches(Method method, Class<?> cls, Object[] args) {//動態匹配 System.out.println("對"+cls.getName()+"類的"+method.getName()+"方法作動態檢查"); String clientName=args[0].toString(); return specialCients.contains(clientName); } }
匹配規則:目標類爲Waiter或其子類,方法名爲serve,動態傳入的參數name必須在白名單中存在。
<bean id='dynamicAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'> <property name="pointcut"> <bean class='com.GreetingDynamicPointcut'/> </property> <property name="advice"> <bean class='com.GreetingBeforeAdvice'/> </property> </bean> <bean id='waiter1' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='dynamicAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter1"); waiter.serve("Peter"); waiter.check("Peter"); waiter.serve("TheViper"); waiter.check("TheViper");
對com.Waiter類作靜態檢查 對com.Waiter類的serve方法作靜態檢查 對com.Waiter類作靜態檢查 對com.Waiter類的check方法作靜態檢查 對com.Waiter類作靜態檢查 對com.Waiter類的clone方法作靜態檢查 對com.Waiter類作靜態檢查 對com.Waiter類的toString方法作靜態檢查 //上面是織入前spring對目標類中的全部方法進行的靜態切點檢查 對com.Waiter類作靜態檢查 對com.Waiter類的serve方法作靜態檢查 對com.Waiter類的serve方法作動態檢查 waiter說:要點什麼?Peter 對com.Waiter類作靜態檢查 對com.Waiter類的check方法作靜態檢查 //靜態方法檢查沒經過,不用動態檢查了 waiter說:結帳?Peter 對com.Waiter類的serve方法作動態檢查 //第二次調用不用執行靜態檢查 你好!TheViper //動態檢查,知足白名單,執行前置加強 waiter說:要點什麼?TheViper waiter說:結帳?TheViper
定義動態切點時,切勿忘記同時覆蓋getClassFilter()和matches(Method method,Class cls)方法,經過靜態切點檢查能夠排除掉大部分不符合匹配規則的方法。
spring的流程切面由DefaultPointcutAdvisor
和ControlFlowPointcut
實現。流程切點表明由某個方法直接或間接發起調用的其餘方法。
定義Waiter的代理
public class WaiterDelegate { private Waiter waiter; public void setWaiter(Waiter waiter) { this.waiter = waiter; } public void service(String name){ waiter.serve(name); waiter.check(name); } }
<bean id='controlFlowPointcut' class='org.springframework.aop.support.ControlFlowPointcut'> <constructor-arg type='java.lang.Class' value='com.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='greetBeforeAdvice'/> <bean id='waiter2' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='controlFlowAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter2"); WaiterDelegate wd=new WaiterDelegate(); wd.setWaiter(waiter); waiter.serve("TheViper"); waiter.check("TheViper"); wd.service("TheViper");
waiter說:要點什麼?TheViper waiter說:結帳?TheViper //直接調用,加強不起做用 你好!TheViper waiter說:要點什麼?TheViper 你好!TheViper waiter說:結帳?TheViper
spring提供ComposablePointcut
把兩個切點組合起來,經過切點的複合運算表示。ComposablePointcut
自己也是一個切點,它實現了Pointcut
接口。
交集運算的方法
並集運算
import java.lang.reflect.Method; import org.springframework.aop.MethodMatcher; 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"); MethodMatcher pt2=new NameMatchMethodPointcut(){ public boolean matches(Method method, Class<?> cls) {// 切點方法靜態匹配 return "check".equals(method.getName()); } }; return cp.intersection(pt1).intersection(pt2); } }
<bean id='gcp' class='com.GreetingComposablePointcut'></bean> <bean id='composableAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor' p:pointcut='#{gcp.intersectionPointcut}' p:advice-ref='greetBeforeAdvice'></bean> <bean id='waiter3' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='composableAdvisor' p:target-ref='waiterTarget'/>
\ #{gcp.intersectionPointcut}表示引用gcp.getIntersectionPointcut()方法返回的複合切點
Waiter waiter=(Waiter)ctx.getBean("waiter3"); WaiterDelegate wd=new WaiterDelegate(); wd.setWaiter(waiter); waiter.serve("TheViper"); waiter.check("TheViper"); wd.service("TheViper");
waiter說:要點什麼?TheViper waiter說:結帳?TheViper //直接調用,加強不起做用 waiter說:要點什麼?TheViper 你好!TheViper//匹配check方法 waiter說:結帳?TheViper