aop:面向切面編程。
我的理解就是:對於多個執行個體對象,把它們碼放在一起,在一個或多個特定位置切上一刀,在這個切面的位置上做一些統一的操作。
aop最常用的情境就是日誌。
一般來說我們希望在某些函數調用前後記錄日誌。最容易想到的辦法如下:package com.red.myaop.bean;<br />public class EasyLog {<br />/**<br /> * @param args<br /> */<br />public static void main(String[] args) {<br />EasyLog easy = new EasyLog();</p><p>System.out.println("do before...");<br />easy.say();<br />System.out.println("do after...");<br />}</p><p>public void say(){</p><p>System.out.println("hello");</p><p>}<br />}
每次在調用函數的前後加上日誌資訊。
但如果要對所有對象調用say()函數進行日誌,照這樣的方法就會出現如下情況。。。。。<br />System.out.println("do before...");<br />easy.say();<br />System.out.println("do after...");<br />System.out.println("do before...");<br />easy1.say();<br />System.out.println("do after...");<br />System.out.println("do before...");<br />easy2.say();<br />System.out.println("do after...");<br />。。。。。。
這就產生了面向切面的需求,也就是:我要對所有對象在say函數上切一刀,在這個切面上做一些日誌。
現利用java的動態代理方法來實現對函數調用進行代理,可以在代理函數調用中對實際的調用進行日誌。
假設有一個介面聲明了函數say:
package com.red.myaop.bean;<br />public interface ISay {<br />public void say();<br />}
有兩個類分別實現了該介面:
package com.red.myaop.bean;<br />public class Hello implements ISay{<br />@Override<br />public void say() {<br />System.out.println("Hello!");<br />}<br />}
package com.red.myaop.bean;<br />public class Bye implements ISay {<br />@Override<br />public void say() {<br />System.out.println("Bye bye!");<br />}<br />}
我希望這兩個類的執行個體在調用say的前後都能做一下日誌。
可以用到java反射中的一些編程介面。
最核心的的方法為,調用
Object
java.lang.reflect.Proxy
.newProxyInstance(ClassLoader
loader, Class
<?>[]
interfaces, InvocationHandler
h) throws IllegalArgumentException
它是一個靜態方法,需要提供一個類載入器,介面和一個實現InvocationHandler的代理執行個體,返回一個對應的代理類。
其中第一個參數表示被代理的對象的類的載入器,這裡指的就是Hello和Bye的類載入器,第二個參數表示被代理類的介面,這裡指的就是ISay。最後一個參數是一個實現InvocationHandler的代理執行個體,他需要實現
Object
invoke(Object
proxy, Method
method, Object
[]
arg2) throws Throwable
。
其中proxy就是這個代理本身(可以暫時這樣理解,實際有區別)。method就是被代理的對象的當前被調用函數,arg2是這個被調用函數的參數。
我要做的事情是這樣的,一般而言產生一個執行個體對象往往用new來實現,比如ISay h=new Hello(); 而當調用h.say()時,函數會直接被調用。現在我希望劫持這個對象,讓的函數調用也可以自訂的進行,此處就是讓它在調用say時前後加上日誌。
怎麼劫持這個對象,就是建立一個該對象的代理,當要調用say方法時委託這個代理對象來完成實際say方法的調用,而代理則可以在實際調用say方法時做一些動作。
實現的方法如下:
package com.red.myaop.proxy;<br />import java.lang.reflect.InvocationHandler;<br />import java.lang.reflect.Method;<br />public class MyProxy implements InvocationHandler {<br />private Object delegate;</p><p>public Object getDelegate() {<br />return delegate;<br />}<br />public void setDelegate(Object delegate) {<br />this.delegate = delegate;<br />}<br />@Override<br />public Object invoke(Object proxy, Method method, Object[] arg2)<br />throws Throwable {</p><p>System.out.println("do before...");<br />Object ret = method.invoke(this.delegate, arg2);<br />System.out.println("do after...");<br />return ret;<br />}<br />}<br />
這是一個函數調用代理類,首先它實現了invoke方法。其次他有一個delegate成員,類型是Object,即MyProxy可以成為任何對象的函數調用代理,也就是說他可以劫持任何類對象,使得被劫持對象的某個函數被調用時,自動調用invoke方法,你可以在這個方法內自訂被劫持對象的方法會被怎麼調用,在此處,當被劫持的任何函數被調用的前後都會列印一些日誌。
package com.red.myaop.proxy;<br />import java.lang.reflect.Proxy;<br />public class ProxyFactory {<br />public static Object getInstance(Class clazz) {<br />MyProxy proxy = new MyProxy();<br />try {<br />proxy.setDelegate(clazz.newInstance());<br />} catch (InstantiationException e) {<br />e.printStackTrace();<br />} catch (IllegalAccessException e) {<br />e.printStackTrace();<br />}<br />return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), proxy);<br />}<br />}
這是一個工廠類,只有一個靜態方法,他接受一個Class類型的參數,返回一個該類的代理對象,即這個類對象已經被劫持,返回的是這個劫持者,但是它的外表看上去沒有任何特別,它的類載入器是和原來一樣,並且它實現的介面也和原來一樣。在getInstance裡,首先根據類來建立執行個體,這就和new的效果一樣,然後用MyProxy的執行個體來做它的函數調用代理,即這個對象的任何函數調用都要經proxy來處理。最後返回用Proxy.newProxyInstance方法產生的代理對象。
測試主函數如下:
package com.red.myaop.bean;<br />import com.red.myaop.proxy.ProxyFactory;<br />public class Tester {<br />public static void main(String[] args) {</p><p>ISay hello = (ISay)ProxyFactory.getInstance(Hello.class);<br />ISay bye= (ISay)ProxyFactory.getInstance(Bye.class);</p><p>hello.say();<br />bye.say();</p><p>}<br />}
可見用ProxyFactory產生的對象和用new產生的對象在調用者看來沒有區別,但實際卻被劫持了,所以運行結果為:
do before...
Hello!
do after...
do before...
Bye bye!
do after...
但是j2se標準介面中只提供了對實現介面的類的動態代理,所以被代理類必須實現某個介面,否則不能對某個函數進行代理。如果要對普通類進行動態代理,這裡不做討論。