Java設計模式之代理模式(動態代理部分)

動態代理

Java設計模式之代理模式(動態代理部分)

動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時確定。

在java的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),這一個類和接口是實現我們動態代理所必須用到的。首先我們先來看看java的API幫助文檔是怎麼樣對這兩個類進行描述的:

InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。我們來看看InvocationHandler這個接口的唯一一個方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

我們看到這個方法一共接受三個參數,那麼這三個參數分別代表什麼呢?

  • proxy: 指代我們所代理的那個真實對象
  • method: 指代的是我們所要調用真實對象的某個方法的Method對象
  • args: 指代的是調用真實對象某個方法時接收的參數

接下來我們來看看Proxy這個類:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,

// 方法 1: 該方法用於獲取指定代理對象所關聯的調用處理器

static InvocationHandler getInvocationHandler(Object proxy)

// 方法 2:該方法用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象

static Class getProxyClass(ClassLoader loader, Class[] interfaces)

// 方法 3:該方法用於判斷指定類對象是否是一個動態代理類

static boolean isProxyClass(Class cl)

// 方法 4:該方法用於為指定類裝載器、一組接口及調用處理器生成動態代理類實例

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

但是我們用的最多的就是 newProxyInstance 這個方法:

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

這個方法的作用就是得到一個動態的代理對象,其接收三個參數,我們來看看這三個參數所代表的含義:

  • loader: 一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進行加載
  • interfaces: 一個Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什麼接口,如果我提供了一組接口給它,那麼這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了
  • h: 一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上

好了,在介紹完這兩個接口(類)以後,我們來通過一個實例來看看我們的動態代理模式是什麼樣的:

首先我們定義了一個Subject類型的接口,為其聲明瞭兩個方法:

public interface Subject {

public void rent(); public void hello(String str);

}

接著,定義了一個類來實現這個接口,這個類就是我們的真實對象,RealSubject類:

public class RealSubject implements Subject {

@Override public void rent() { System.out.println("I want to rent my house"); } @Override public void hello(String str) { System.out.println("hello: " + str); }

}

下一步,我們就要定義一個動態代理類了,前面說個,每一個動態代理類都必須要實現 InvocationHandler 這個接口,因此我們這個動態代理類也不例外:

public class DynamicProxy implements InvocationHandler {

// 這個就是我們要代理的真實對象 private Object subject; // 構造方法,給我們要代理的真實對象賦初值 public DynamicProxy(Object subject) { this.subject = subject; }@Overridepublic Object invoke(Object object, Method method, Object[] args) throws Throwable {// 在代理真實對象前我們可以添加一些自己的操作System.out.println("before rent house"); System.out.println("Method:" + method); //當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用 method.invoke(subject, args); // 在代理真實對象後我們也可以添加一些自己的操作 System.out.println("after rent house"); return null; }

}

最後,來看看我們的Client類:

通過Proxy的newProxyInstance方法來創建我們的代理對象,我們來看看其三個參數

* 第一個參數 handler.getClass().getClassLoader() ,我們這裡使用handler這個類的ClassLoader對象來加載我們的代理對象

* 第二個參數realSubject.getClass().getInterfaces(),我們這裡為代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了

* 第三個參數handler, 我們這裡將這個代理對象關聯到了上方的 InvocationHandler 這個對象上

public class Client {

public static void main(String[] args) {// 我們要代理的真實對象Subject realSubject = new RealSubject();// 我們要代理哪個真實對象,就將該對象傳進去,最後是通過該真實對象來調用其方法的InvocationHandler handler = new DynamicProxy(realSubject);Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler);System.out.println(subject.getClass().getName()); subject.rent(); subject.hello("world"); }

}

我們先來看看控制檯的輸出:

$Proxy0

before rent house

Method:public abstract void com.yunsheng.dynamicproxy.Subject.rent()

I want to rent my house after rent house before rent house

Method:public abstract void com.yunsheng.dynamicproxy.Subject.hello(java.lang.String)

hello: world

after rent house

我們首先來看看

$Proxy0

這東西,我們看到,這個東西是由

System.out.println(subject.getClass().getName());

這條語句打印出來的,那麼為什麼我們返回的這個代理對象的類名是這樣的呢?

原因是通過 Proxy.newProxyInstance 創建的代理對象是在jvm運行時動態生成的一個對象,它並不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型,而是在運行是動態生成的一個對象,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示對象的標號。

其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並不是每次調用 Proxy 的靜態方法創建動態代理類都會使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重複創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象,而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重複生成,提高了代理類的創建效率。

具體來講,是這樣的:

1. 產生代理類$Proxy0類 執行了Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 將產生$Proxy0類,它繼承Proxy對象,並根據第二個參數,實現了被代理類的所有接口,自然就可以生成接口要實現的所有方法了(這時候會重寫hashcode,toString和equals三個方法),但是還沒有具體的實現體;

2. 將代理類$Proxy0類加載到JVM中 這時候是根據Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第一個參數----就是被代理類的類加載器,把當前的代理類加載到JVM中

3. 創建代理類$Proxy0類的對象 調用的$Proxy0類的$Proxy0(InvocationHandler)構造函數,生成$Proxy0類的對象 參數就是Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第三個參數 這個參數就是我們自己實現的InvocationHandler對象,$Proxy0對象調用所有要實現的接口的方法,都會調用InvocationHandler對象的invoke()方法實現; 然後InvocationHandler對象中又組合加入了代理類代理的接口類的實現類;所以在invoke方法中可以調用真實對象的方法。

4. 生成代理類的class byte 動態代理生成的都是二進制class字節碼 接著我們來看看這兩句


分享到:


相關文章: