AOP = Aspect Oriental Programing 面向切面編程java
文章裏不講AOP術語,什麼鏈接點、切點、切面什麼的,這玩意太繞,記不住也罷。旨在以簡單、直白的方式理解AOP,理解Spring AOP, 應用 @AspectJ。正則表達式
一、什麼是AOP?spring
方法1 | 方法2 | 方法3 |
A | A | A |
代碼x | 代碼y | 代碼z |
B | B | B |
從縱向看,方法一、二、3 都執行了相同的A、B代碼,這樣重複代碼是很無聊的。數據庫
一個典型的場景就是:開啓事務,更新表裏數據,提交事務; 開啓事務,刪除表裏數據,提交事務。編程
因此咱們從橫向來,把重複代碼抽取出來,變爲框架
A | A | A |
方法1(代碼x) | 方法2(代碼y) | 方法3(代碼z) |
B | B | B |
AOP但願將A、B 這些分散在各個業務邏輯中的相同代碼,經過橫向切割的方式抽取到一個獨立的模塊中,還業務邏輯類一個清新的世界。ide
固然,將這些重複性的橫切邏輯獨立出來很容易,可是如何將獨立的橫切邏輯 融合到 業務邏輯中 來完成和原來同樣的業務操做,這是事情的關鍵,也是AOP要解決的主要問題。測試
2.Spring AOP 實現機制優化
Spring AOP使用動態代理技術在運行期織入加強的代碼,使用了兩種代理機制,一種是基於JDK的動態代理,另外一種是基於CGLib的動態代理。this
織入、加強 是AOP的兩個術語,織入加強的代碼簡單點就是在你的代碼上插入另外一段代碼。
JDK動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy 和 InvocationHandler(接口)。
直接上代碼
package test; public interface CalcService { public void add(int x, int y); }
package test; public class CalcServiceImpl implements CalcService{ public void add(int x, int y) { System.out.println("結果爲" + (x + y)); } }
package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class CalcHandler implements InvocationHandler { public Object target; public CalcHandler(Object target){ this.target = target; } /** * 實現接口的方法 * @param proxy 最終生成的代理實例 * @param method 被代理目標(也就是target)的某個具體方法 * @param args 某個具體方法的入參參數 * @return Object 方法返回的值*/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("*******調用方法前執行代碼******"); Object obj = method.invoke(this.target, args); System.out.println("*******調用方法後執行代碼******"); return obj; } }
package test; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args){ long start = System.nanoTime(); CalcService target = new CalcServiceImpl(); CalcHandler handler = new CalcHandler(target); CalcService calcProxy = (CalcService)Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); System.out.println("建立時間:" + (System.nanoTime()-start)); start = System.nanoTime(); calcProxy.add(2, 3); System.out.println("執行時間:" + (System.nanoTime()-start)); } }
執行結果爲
*******調用方法前執行代碼******
結果爲2
*******調用方法後執行代碼******
可是JDK動態代理有一個限制,即它只能爲接口建立代理實例*******************************。
看Proxy的方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
interfaces 是須要代理實例實現的接口列表
那麼對於一個沒有經過接口定義業務方法的類,怎麼建立代理實例?
CGLib
CGLib採用很是底層的字節碼技術,能夠在運行時爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用。
package test; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CalcProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ enhancer.setSuperclass(clazz); // 設置須要被代理的類 target enhancer.setCallback(this); return enhancer.create(); // 經過字節碼技術動態建立子類 } // 攔截父類全部方法的調用 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("*******調用方法前執行代碼******"); Object result = proxy.invokeSuper(obj, args); // 經過代理類調用父類中的方法 System.out.println("*******調用方法後執行代碼******"); return result; } }
package com.ycp.framework.test.proxyPattern.sample2; public class Test2 { public static void main(String[] args) { CalcProxy proxy = new CalcProxy(); CalcServiceImpl calcImpl = (CalcServiceImpl)proxy.getProxy(CalcServiceImpl.class); calcImpl.add(2, 3); } }
以後對二者作了一個效率對比
我在本身本機上經過System.nanoTime()對二者作了記錄,結果以下
JDK動態代理 CGLiib
建立代理對象時間 720 1394 1 3473 7007 (時間單位爲納秒)
代理對象執行方法時間 97 7322 15 2080
一個建立花費時間長,一個執行時間長。
3.使用 Spring AOP,並經過XML配置
在Spring中,定義了 AopProxy接口,並提供了兩個final類型的實現類
Cglib2AopProxy JdkDynamicAopProxy
以一個前置加強爲例,也就是說在目標方法執行前執行的代碼
package test; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class CalcBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object obj) throws Throwable { System.out.println("*******調用目標方法前執行代碼******"); } public static void main (String [] args){ CalcService target = new CalcServiceImpl(); CalcBeforeAdvice advice = new CalcBeforeAdvice(); // 1 spring 提供的代理工廠 ProxyFactory pf = new ProxyFactory(); // 2 設置代理目標 pf.setInterfaces(target.getClass().getInterfaces());// 指定對接口進行代理,將使用JdkDynamicAopProxy // 下面兩行操做,有任意一行,都將使用Cglib2AopProxy pf.setOptimize(true);// 啓用代理優化,將使用Cglib2AopProxy pf.setProxyTargetClass(true); // true表示對類進行代理,將使用Cglib2AopProxy pf.setTarget(target); // 3 爲代理目標添加加強 pf.addAdvice(advice); // 4 生成代理實例 CalcService proxy = (CalcService) pf.getProxy(); System.out.println(proxy.getClass().getName()); proxy.add(2, 3); } }
以上經過ProxyFactory建立代理,下面咱們經過Spring配置來聲明代理
<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/> <bean id="calcTarget" class="test.CalcServiceImpl"/> <bean id="calcProxy" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="test.CalcService" //指定代理接口,若是有多個接口,可用,隔開 p:interceptorNames="calcBeforAdvice"//指定使用的加強,引用第一行,若是有多個可用,隔開 p:target-ref="calcTarget"//指定對那個Bean進行代理,引用第二行 p:proxyTargetClass="true" //指定是否對類進行代理 p:singleton="true"//指定返回的代理是否單實例,默認爲true />
除了前置加強BeforeAdvice,還有後置加強AfterReturningAdvice、環繞加強MethodInterceptor、異常拋出加強
ThrowsAdvice、及引介加強IntroductionInterceptor,均爲接口。
其中引介加強稍微強調一下,它會在目標類中增長一些新的方法和屬性。
到了這裏,可能對AOP稍有些瞭解了,那咱們簡單說一下AOP的幾個名詞
鏈接點Joinpoint:類初始化前、初始化後, 方法調用前、調用後,方法拋出異常後,這些特定的點,叫鏈接點。
切點Pointcut:想一想數據庫查詢,切點就是經過其所設定的條件找到對應的鏈接點。
加強Advice:就是把代碼加到某個鏈接點上。
引介Introduction:一種特殊的加強,它爲類增長一些屬性和方法,假設某個業務類沒有實現A接口,咱們給它添加方法,讓其成爲A的實現類。
織入Weaving:就是怎麼將加強添加到鏈接點上。
三種織入方式:一、編譯期織入,要求使用特殊的JAVA編譯器
2.類裝載期織入,要求使用特殊的類裝載器
3.動態代理織入,在運行期爲目標類添加加強
Spring採用動態代理,而AspectJ採用編譯期織入和類裝載期織入。
目標對象Target:也就是你本身的業務類,AOP就是對這個類作加強、引介。
代理Proxy: 目標對象被織入加強後產生的結果類。
切面:由切點和加強(引介)組成,Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯(也就是代碼)織入到切面所指定的鏈接點(也就是代碼往哪加)中。
看完了名詞,再看完以前的代碼,咱們發現加強被織入到了目標類的全部方法中(XX的,都木有選擇的餘地....)
如今咱們要對某些類的某些方法織入加強,那這時候就涉及到切點概念了
以以下爲例:我只想針對全部的以add開頭的方法作處理
靜態普通方法名匹配
package com.ycp.framework.test.proxyPattern.sample2; import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.IntroductionInterceptor; import org.springframework.aop.ThrowsAdvice; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; public class AddAdvisor extends StaticMethodMatcherPointcutAdvisor { //StaticMethodMatcherPointcutAdvisor 抽象類 // 實現父類 matches方法 public boolean matches(Method method, Class clazz) { //只匹配add方法 return 0 == "add".indexOf(method.getName()); } // //切點類匹配規則爲 CalcServiceImpl的類或子類, // @Override // public ClassFilter getClassFilter(){ // return new ClassFilter(){ // public boolean matches(Class clazz){ // return CalcServiceImpl.class.isAssignableFrom(clazz); // } // }; // } public static void main (String [] args){ CalcService target = new CalcServiceImpl(); CalcBeforeAdvice advice = new CalcBeforeAdvice(); // 1 spring 提供的代理工廠 ProxyFactory pf = new ProxyFactory(); // 2 設置代理目標 pf.setInterfaces(target.getClass().getInterfaces());// 指定對接口進行代理,將使用JdkDynamicAopProxy // 下面兩行操做,有任意一行,都將使用Cglib2AopProxy pf.setOptimize(true);// 啓用代理優化,將使用Cglib2AopProxy pf.setProxyTargetClass(true); // true表示對類進行代理,將使用Cglib2AopProxy pf.setTarget(target); // 3 爲代理目標添加加強 AddAdvisor advisor = new AddAdvisor(); advisor.setAdvice(advice); pf.addAdvisor(advisor); // 4 生成代理實例 CalcService proxy = (CalcService) pf.getProxy(); System.out.println(proxy.getClass().getName()); proxy.add(2, 3); } }
經過Spring配置來定義切面
<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/> <bean id="calcTarget" class="test.CalcServiceImpl"/> <bean id="addAdvisor" class="test.AddAdvisor" p:advice-ref="calcBeforAdvice"//向切面注入一個前置加強 /> <bean id="parent" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="addAdvisor" p:proxyTargetClass="true" /> <bean id="calc" parent="parent" p:target-ref="calcTarget"/> //CalcServiceImpl的代理
上面的忒麻煩,咱們經過靜態正則表達式來匹配
<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/> <bean id="calcTarget" class="test.CalcServiceImpl"/> <bean id="addRegexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:advice-ref="calcBeforAdvice"//向切面注入一個前置加強 > <property name="patterns"> <list> <value> .add*</value>//匹配模式串 </list> </property> </bean> <bean id="calc" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="addRegexpAdvisor" p:target-ref="calcTarget" p:proxyTargetClass="true" />
Spring提供了6種類型的切點,靜態方法切點、動態方法切點、註解切點、表達式切點、流程切點,我能力有限,沒有研究下去,僅以靜態切點 StaticMethodMatcherPointcut 作個例子就算完事,啥時項目用到了啥時再研究吧。
4.使用AspectJ
Spring AOP應用是比較麻煩的,要實現這個那個接口,寫這個那個XML描述,你頭疼不?
使用@AspectJ的註解能夠很是容易的定義一個切面,不須要實現任何的接口