Java Reflection(十一):動態代理
利用Java反射機製你可以在運行期動態的創建接口的實現。java.lang.reflect.Proxy類就可以實現這一功能。這個類的名字(譯者注:Proxy意思為代理)就是為什麼把動態接口實現叫做動態代理。動態的代理的用途十分廣泛,比如數據庫連接和事物管理(transaction management)還有單元測試時用到的動態mock對象以及AOP中的方法攔截功能等等都使用到了動態代理。
創建代理
你可以通過使用Proxy.newProxyInstance()方法創建動態代理。newProxyInstance()方法有三個參數:
1、類加載器(ClassLoader)用來加載動態代理類。
2、一個要實現的接口的數組。
3、一個InvocationHandler把所有方法的調用都轉到代理上。
如下例:
InvocationHandler handler = new MyInvocationHandler(); MyInterface proxy = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), new Class[] { MyInterface.class }, handler);
在執行完這段代碼之後,變量proxy包含一個MyInterface接口的的動態實現。所有對proxy的調用都被轉向到實現了InvocationHandler接口的handler上。有關InvocationHandler的內容會在下一段介紹。
InvocationHandler接口
在前麵提到了當你調用Proxy.newProxyInstance()方法時,你必須要傳入一個InvocationHandler接口的實現。所有對動態代理對象的方法調用都會被轉向到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參數是實現要代理接口的動態代理對象。通常你是不需要他的。
invoke()方法中的Method對象參數代表了被動態代理的接口中要調用的方法,從這個method對象中你可以獲取到這個方法名字,方法的參數,參數類型等等信息。關於這部分內容可以查閱之前有關Method的文章。
Object數組參數包含了被動態代理的方法需要的方法參數。注意:原生數據類型(如int,long等等)方法參數傳入等價的包裝對象(如Integer, Long等等)。
常見用例
動態代理常被應用到以下幾種情況中
- 數據庫連接以及事物管理
- 單元測試中的動態Mock對象
- 自定義工廠與依賴注入(DI)容器之間的適配器
- 類似AOP的方法攔截器
數據庫連接以及事物管理
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();
單元測試中的動態Mock對象
Butterfly Testing工具通過動態代理來動態實現樁(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方法的返回值來判斷的。
自定義工廠與依賴注入(DI)容器之間的適配器
依賴注入容器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容器在運行期當成一個工廠使用,比起在創建這個類的時候進行注入,這種方式顯然更好。而且這種方法沒有依賴到Butterfly Container中的任何接口。
類似AOP的方法攔截器
Spring框架可以攔截指定bean的方法調用,你隻需提供這個bean繼承的接口。Spring使用動態代理來包裝bean。所有對bean中方法的調用都會被代理攔截。代理可以判斷在調用實際方法之前是否需要調用其他方法或者調用其他對象的方法,還可以在bean的方法調用完畢之後再調用其他的代理方法。
最後更新:2017-05-23 11:31:50