標籤:aop cglib spring 事件 編程
一、 引言
AOP(Aspect-Oriented Programming,面向切面的編程),是一種新型的編程範式,主張關注軟體流程中的一個切面,將同樣功能的代碼整合打包在一起,降低系統的耦合性,增強其擴充性。
傳統的軟體設計,往往採取事件驅動模型帶來類似的效果,通過在可能的事件切入點插入事件回呼函數,將對應位置插入外置代碼。
函數式編程,也有類似的解決方案,通過函數傳遞,將對應位置的擴充上新的功能。
Java作為一門嚴謹的傳統式開發語言,以安全性和可靠性為第一標準,語言並沒有過多的新特性支援,Java8僅支援到lambda運算式,為了使Java具有更強大的編程模型,Spring等架構使用gclib庫實現了面向切面的編程模型。
二、 CGLIB 和 ASM
CGLIB 是一個強大的,高效能,高品質的Code產生類庫,被廣泛的用作動態代理技術。CGLIB 包的底層是通過使用一個小而快的位元組碼處理架構ASM,來轉換位元組碼並產生新的類,新的位元組碼可以被Java虛擬機器直接載入運行。
其實動態代理並不是CGLIB的專利,早在JDK1.3版起,就引入了動態代理庫,Spring AOP 編程時就可以進行選擇,使用JDK提供的動態代理庫,或者是引入CGLIB庫。
下面舉一個執行個體,來說明一些如何使用CGLIB庫,將我們本來應該正常執行的函數調用,進行截斷操作。
package com.abs.testcglib;public class Service { String name; public Service(String name) { this.name = name; } public void sayHello() { System.out.println("Hello "+name); }}
首先,我們建立一個服務類,其中有一個sayHello()
方法,我們希望將這個方法截斷,以添加其餘組件的一些處理功能,例如持久化組件希望在此添加一條記錄一類的功能。
package com.abs.testcglib;public class Main { public static void main(String[] args) { Service s = new Service("Sxf"); s.sayHello(); }}
在Main函數中調用一下,可以看的Hello Sxf
的輸出。
但我們怎麼截斷呢?首先就要建立一個代理類,所謂代理,就是你讓這個代理類,代你調用這個類的函數。
建立一個代理類:
package com.abs.testcglib;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CglibProxy implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("方法名:"+method.getName()); Service a = (Service) o; a.name = "Wah"; System.out.println("哈哈,我要改名"); Object result = methodProxy.invokeSuper(o, args); return result; }}
這個代理類的功能,就是將傳統的Java直接的函數調用,包上一次外殼,因為Java本身的函數調用是系統完成的,很難由你大段他,但代理類不同,你可以明確的看的調用了哪個函數,並且可以根據這點,輕鬆的在函數調用前後,插入你希望插入的代碼。
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
這個函數大概是整個代理調用中最關鍵的一個函數了,o這個參數表示了該函數所在的對象,args是調用的參數,Method則是反射到的方法。最後一個則是代理的執行個體。
我們對函數的打斷功能,則都在這個函數裡實現。
當然,由於是通過代理實現,對象的構建也有所不同,所以我們自己寫一個static函數作為建構函式使用。
package com.abs.testcglib;import net.sf.cglib.proxy.Enhancer;public class Service { String name; public Service(String name) { this.name = name; } public void sayHello() { System.out.println("Hello "+name); } public static Service getProxyInstance(CglibProxy myProxy, String name) { Enhancer en = new Enhancer(); // 設定父類和回調 en.setSuperclass(Service.class); en.setCallback(myProxy); // 調用其建構函式,需要傳入對應的Class列表和參數Object列表 return (Service) en.create(new Class[] {String.class}, new Object[] {name}); }}
而Main函數中也應該這樣使用該對象:
package com.abs.testcglib;public class Main { public static void main(String[] args) { Service s = new Service("Sxf"); s.sayHello(); Service s2 = Service.getProxyInstance(new CglibProxy(), "Sxf"); s2.sayHello(); }}
我們發現,兩種方式建立出的對象,使用上幾乎一樣,唯一不同的就是建構函式時,我們進行了部分修改,其餘部分,不影響我們的對象正常傳遞,儲存等功能。
最終效果:
三、 Spring AOP 的實現
其實看來剛才CGLIB的實現,再看著名的Spring架構,就會發現兩者的實現方式幾乎完全一樣,只不過Spring架構多增加了一些概念和功能。
下面我們寫一個Target 類,這是一個被代理的目標對象,其中有一個execute()
方法,現在使用 AOP 對 execute()
方法做日誌輸出。在執行execute()
方法前,做日誌輸出。
public class Target { public void execute(String name){ System.out.println("executeMethod is here" + name); }}
通知可以攔截目標對象的 execute()方法,並執行日誌輸出。建立通知的代碼如下:
public class LoggerExecute implements MethodInterceptor { public Object invoke(MethodInvocation arg0) throws Throwable { before(); arg0.proceed(); return null; } private void before() { System.out.println("executeMethod is exe!"); }}
建立代理的方法也幾乎一樣:
public static void main(String[] args) { //建立目標對象 Target target = new Target(); //建立代理 ProxyFactory di=new ProxyFactory(); di.addAdvice(new BeforeExecute()); di.setTarget(target); Target proxy=(Target)di.getProxy(); //代理執行execute()方法 proxy.execute(" ni hao");}
當然Spring的切入點和其設定檔關聯十分緊密,用Spring架構能夠將系統的更多固定參數丟到設定檔中去,或者直接使用註解也可以。
Java的面向AOP編程