最近在學習Spring。某大人跟我說,Spring的AOP其實就是Java反射中的動態代理。OK,那我就從動態代理開始看起。
一、基本概念
所謂動態代理,基本上是如下場景:假設我有個接口IHelloWorld
public interface IHelloWorld{
void sayHello();
}
我再有一個實現類HelloWorldImpl實現了IHelloWorld接口
public class HelloWorldImpl implements IHelloWorld{
public void sayHello(){
System.out.println("Hello, World");
}
}
這樣,我就可以創建一個HelloWorldImpl對象,來實現IHelloWorld中定義的服務。
問題是,現在,我打算為HelloWorldImpl增強功能,需要在調用sayHello方法前后各執行一些操作。在有些情況下,你無法修改HelloWorldImpl的源代碼,那怎么辦呢?
從道理上來說,我們可以攔截對HelloWorldImpl對象里sayHello()函數的調用。也就是說,每當有代碼調用sayHello函數時,我們都把這種調用請求攔截下來之后,做自己想做的事情。
那怎么攔截呢?
首先,需要開發一個InvocationHandler。這個東東表示的是,你攔截下函數調用之后,究竟想干什么。InvocationHandler是一個接口,里面的聲明的函數只有一個:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
這個函數表示一次被攔截的函數調用。因此,proxy表示這個被攔截的調用,原本是對哪個對象調用的;method表示這個被攔截的調用,究竟是調用什么方法;args表示這個被攔截的調用里,參數分別是什么。
我們下面寫一個攔截器,讓他在函數調用之前和之后分別輸出一句話。
import java.lang.reflect.*;
public class HelloHandler implements InvocationHandler{
Object oriObj;
public HelloProxy(Object obj){
oriObj = obj;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
Object result = null;
//在函數調用前輸出一些信息
System.out.println("################################");
String methodName = m.getName();
System.out.println("method name : " + methodName);
doBefore();
//利用反射,進行真正的調用
result = m.invoke(oriObj, args);
//在函數調用后執行
doAfter();
System.out.println("################################");
return result;
}
public void doBefore(){
System.out.println("Do Before");
}
public void doAfter(){
System.out.println("Do After");
}
}
有了這個Handler之后,下面要做的,就是把這個Handler和一個IHelloWorld類型的對象裝配起來。重點的函數只有一個,那就是java.lang.reflect.Proxy類中的一個靜態工廠方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
這個方法返回一個對象,我們稱返回的對象為代理對象(proxy)。
而后,我們就不把真正的原對象暴露給外接,而使用這個代理對象。這個代理對象接受對源對象的一切函數調用(也就是把所有調用都攔截了),然后根據我們寫的InvocationHandler,來對函數進行處理。
產生代理對象的過程,我把它理解成一個裝配的過程:由源對象、源對象實現的接口、InvocationHandler裝配產生一個代理對象。
相應的測試代碼如下:
public class TestHello{
public static void main(String args[])throws Exception{
HelloWorldImpl h = new HelloWorldImpl();
Object proxy = Proxy.newProxyInstance(
h.getClass().getClassLoader(),
new Class[]{IHelloWorld.class},
new HelloProxy(h)
);
((IHelloWorld)proxy).sayHello();
}
}
利用ant編譯運行的結果:
[java] ################################
[java] method name : sayHello
[java] Do Before
[java] Hello, World
[java] Do After
[java] ################################
二、更多理解
我們看產生代理對象的newProxyInstance函數的聲明:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
這個函數的第一個參數是ClassLoader,第三個參數是InvocationHandler,基本都沒什么問題。
第二個參數是一個Class類型的數組,名字叫interfaces,表示的是產生的動態代理對象實現的接口。
仔細想想,有兩個問題。第一,產生一個代理對象,需要源對象么?第二,我能不能產生一個動態代理對象,來實現源對象沒有實現的接口?
第一個問題和第二個問題其實是一致的。我們完全可以脫離源對象,而直接產生一個代理對象,也可以利用動態代理,讓源對象實現更多的接口,為源對象增強功能。
例如,假設我們希望讓源對象實現java.io.Closeable接口,則首先修改一下我們的Handler的invoke方法,讓他在獲取colse方法時,不要傳遞給源對象(因為源對象沒有實現該方法):
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
Object result = null;
//在函數調用前輸出一些信息
System.out.println("################################");
String methodName = m.getName();
System.out.println("method name : " + methodName);
doBefore();
//判斷是否是Closeabled的方法
if (m.getDeclaringClass().isAssignableFrom(java.io.Closeable.class)){
System.out.println("I got the close() method!");
}else{
//傳遞給源對象
//利用反射,進行真正的調用
result = m.invoke(oriObj, args);
}
//在函數調用后執行
doAfter();
System.out.println("################################");
return result;
}
然后,我們在裝配的過程中,改變一下參數,并強轉之后調用一下close方法:
Object proxy = Proxy.newProxyInstance(
h.getClass().getClassLoader(),
new Class[]{IHelloWorld.class,java.io.Closeable.class},
new HelloProxy(h)
);
((Closeable)proxy).close();
ant運行結果:
[java] ################################
[java] method name : close
[java] Do Before
[java] I got the close() method!
[java] Do After
[java] ################################
三、更多的代理~
我們現在能夠讓sayHello()函數執行之前和之后,輸出一些內容了。那如果我還想在裝配一個Handler呢?
最簡單的方法:
Object proxy = Proxy.newProxyInstance(
h.getClass().getClassLoader(),
new Class[]{IHelloWorld.class, java.io.Closeable.class},
new HelloProxy(h)
);
Object proxy2 = Proxy.newProxyInstance(
h.getClass().getClassLoader(),
new Class[]{IHelloWorld.class, java.io.Closeable.class},
new HelloProxy(proxy)
);
((IHelloWorld)proxy2).sayHello();
ant運行結果:
[java] ################################
[java] method name : sayHello
[java] Do Before
[java] ################################
[java] method name : sayHello
[java] Do Before
[java] Hello, World
[java] Do After
[java] ################################
[java] Do After
[java] ################################
不用我解釋了吧!