作者: Jakob Jenkov 譯者:葉文海(yewenhai@gmail.com)
利用Java反射機制你可以在運行期動態(tài)的創(chuàng)建接口的實現。 java.lang.reflect.Proxy 類就可以實現這一功能。這個類的名字(譯者注:Proxy 意思為代理)就是為什么把動態(tài)接口實現叫做動態(tài)代理。動態(tài)的代理的用途十分廣泛,比如數據庫連接和事物管理(transaction management)還有單元測試時用到的動態(tài) mock 對象以及 AOP 中的方法攔截功能等等都使用到了動態(tài)代理。
你可以通過使用 Proxy.newProxyInstance()方法創(chuàng)建動態(tài)代理。 newProxyInstance()方法有三個參數: 1、類加載器(ClassLoader)用來加載動態(tài)代理類。 2、一個要實現的接口的數組。 3、一個 InvocationHandler 把所有方法的調用都轉到代理上。 如下例:
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
在執(zhí)行完這段代碼之后,變量 proxy 包含一個 MyInterface 接口的的動態(tài)實現。所有對 proxy 的調用都被轉向到實現了 InvocationHandler 接口的 handler 上。有關 InvocationHandler 的內容會在下一段介紹。
在前面提到了當你調用 Proxy.newProxyInstance()方法時,你必須要傳入一個 InvocationHandler 接口的實現。所有對動態(tài)代理對象的方法調用都會被轉向到 InvocationHandler 接口的實現上,下面是 InvocationHandler 接口的定義:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
下面是它的實現類的定義:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}
傳入 invoke()方法中的 proxy 參數是實現要代理接口的動態(tài)代理對象。通常你是不需要他的。
invoke()方法中的 Method 對象參數代表了被動態(tài)代理的接口中要調用的方法,從這個 method 對象中你可以獲取到這個方法名字,方法的參數,參數類型等等信息。關于這部分內容可以查閱之前有關 Method 的文章。
Object 數組參數包含了被動態(tài)代理的方法需要的方法參數。注意:原生數據類型(如int,long等等)方法參數傳入等價的包裝對象(如Integer, Long等等)。
動態(tài)代理常被應用到以下幾種情況中
Spring 框架中有一個事物代理可以讓你提交/回滾一個事物。它的具體原理在 Advanced Connection and Transaction Demarcation and Propagation 一文中有詳細描述,所以在這里我就簡短的描述一下,方法調用序列如下:
web controller --> proxy.execute(...);
proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();
Butterfly Testing工具通過動態(tài)代理來動態(tài)實現樁(stub),mock 和代理類來進行單元測試。在測試類A的時候如果用到了接口 B,你可以傳給 A 一個實現了 B 接口的 mock 來代替實際的 B 接口實現。所有對接口B的方法調用都會被記錄,你可以自己來設置 B 的 mock 中方法的返回值。 而且 Butterfly Testing 工具可以讓你在 B 的 mock 中包裝真實的 B 接口實現,這樣所有調用 mock 的方法都會被記錄,然后把調用轉向到真實的 B 接口實現。這樣你就可以檢查B中方法真實功能的調用情況。例如:你在測試 DAO 時你可以把真實的數據庫連接包裝到 mock 中。這樣的話就與真實的情況一樣,DAO 可以在數據庫中讀寫數據,mock 會把對數據庫的讀寫操作指令都傳給數據庫,你可以通過 mock 來檢查 DAO 是不是以正確的方式來使用數據庫連接,比如你可以檢查是否調用了 connection.close()方法。這種情況是不能簡單的依靠調用 DAO 方法的返回值來判斷的。
依賴注入容器 Butterfly Container 有一個非常強大的特性可以讓你把整個容器注入到這個容器生成的 bean 中。但是,如果你不想依賴這個容器的接口,這個容器可以適配你自己定義的工廠接口。你僅僅需要這個接口而不是接口的實現,這樣這個工廠接口和你的類看起來就像這樣:
public interface IMyFactory {
Bean bean1();
Person person();
...
}
public class MyAction{
protected IMyFactory myFactory= null;
public MyAction(IMyFactory factory){
this.myFactory = factory;
}
public void execute(){
Bean bean = this.myFactory.bean();
Person person = this.myFactory.person();
}
}
當 MyAction 類調用通過容器注入到構造方法中的 IMyFactory 實例的方法時,這個方法調用實際先調用了 IContainer.instance()方法,這個方法可以讓你從容器中獲取實例。這樣這個對象可以把 Butterfly Container 容器在運行期當成一個工廠使用,比起在創(chuàng)建這個類的時候進行注入,這種方式顯然更好。而且這種方法沒有依賴到 Butterfly Container 中的任何接口。
Spring 框架可以攔截指定 bean 的方法調用,你只需提供這個 bean 繼承的接口。Spring 使用動態(tài)代理來包裝 bean。所有對 bean 中方法的調用都會被代理攔截。代理可以判斷在調用實際方法之前是否需要調用其他方法或者調用其他對象的方法,還可以在 bean 的方法調用完畢之后再調用其他的代理方法。
本文鏈接地址: Java Reflection(十一):動態(tài)代理