天天看點

設計模式之動态代理(dynamic proxy)

那麼傳統的靜态代理模式有什麼問題呢?如果需要代理的類隻有一個,那麼靜态代理沒什麼問題,如果有很多類需要代理呢,用靜态代理的話就需要為每一個類建立一個代理類,顯然這麼做太過繁瑣也容易出錯。為此,jdk 5引入的動态代理機制,允許開發人員在運作時刻動态的建立出代理類及其對象。也就是說,我們不用為每個類再單獨建立一個代理對象了。

比如spring的aop架構,它可以通過簡單的配置,在不新增、修改任何業務邏輯代碼情況下,動态的給我們的業務邏輯增加諸如日志列印、事務處理、異常處理等,這就是利用的動态代理機制。

資料庫連接配接以及事物管理

單元測試中的動态 mock 對象

自定義工廠與依賴注入(di)容器之間的擴充卡

類似 aop 的方法攔截器

日志、緩存等業務增強

java rmi遠端通信

各種通路控制器、驗證器

… …

動态代理主要是利用了java的反射機制。

要建立一個動态代理,隻需要利用java api提供的兩個類:

<code>java.lang.reflect.invocationhandler</code>: 這是調用處理器接口,它自定義了一個 <code>invoke()</code> 方法,我們就在這個方法裡觸發代理對象自己的方法,你可以在它的前後增加我們自己的增強方法。

<code>java.lang.reflect.proxy</code>: 這是 java 動态代理機制的主類,它提供了一組靜态方法來為一組接口動态地生成代理類及其對象,也就是動态生成代理對象的方法。

每個代理類的對象都會關聯一個表示内部處理邏輯的<code>invocationhandler</code>接口的實作。當使用者調用了代理對象所代理的接口中的方法的時候,這個調用的資訊會被傳遞給<code>invocationhandler</code>的<code>invoke()</code>方法。在 <code>invoke()</code>方法的參數中可以擷取到代理對象、方法對應的<code>method</code>對象和調用的實際參數。<code>invoke()</code>方法的傳回值被傳回給使用者。這種做法實際上相 當于對方法調用進行了攔截。熟悉aop的人對這種使用模式應該不陌生。但是這種方式不需要依賴aspectj等aop架構。

我們可以通過<code>proxy.newproxyinstance()</code>方法來動态的建立一個代理。這個方法有3個參數:

用<code>proxy</code>類動态建立代理類:

在執行完這段代碼之後,變量proxy 包含一個 myinterface 接口的的動态實作。所有對 proxy 的調用都被轉向到實作了 invocationhandler 接口的 handler 上。有關 invocationhandler 的内容會在下一段介紹。

在前面提到了當你調用<code>proxy.newproxyinstance()</code>方法時,你必須要傳入一個<code>invocationhandler</code>接口的實作。所有對動态代理對象的方法調用都會被轉向到<code>invocationhandler</code>接口的實作上,下面是 invocationhandler 接口的定義:

傳入<code>invoke()</code>方法中的<code>proxy</code>參數是實作要代理接口的動态代理對象。通常你是不需要他的。<code>invoke()</code>方法中的<code>method</code>對象參數代表了被動态代理的接口中要調用的方法,從這個<code>method</code>對象中你可以擷取到這個方法名字,方法的參數,參數類型等等資訊。<code>object</code>數組參數包含了被動态代理的方法需要的方法參數。注意:原生資料類型(如int,long等等)方法參數傳入等價的包裝對象(如integer, long等等)。

比如我們有兩個業務,要為這兩個業務添加日志列印功能。如果是靜态代理,那麼就需要分别為每個業務類寫一個代理類,而如果用動态代理,隻需要實作一個日志列印功能的handler即可,完全不需要自己再單獨寫代理類,下面我們具體看一下這個例子。

接口a和接口b:

接口a和接口b的實作:

運作結果

結果說明

spring 架構中有一個事物代理可以讓你送出/復原一個事物,如果用動态代理的話,其方法調用序列如下: