Java代理模式實現與原理詳解(二)

1 爲什麼要用動態代理

上一次我們詳細分析了靜態代理模式的原理,並且用代碼簡單實現了一個靜態代理的案例。但是我們會發現在靜態代理中代理類與被代理類都需要實現同一個接口,這就說明我們的一個靜態代理類只能代理一個類,並且還要事先知道我們要代理哪個類才能寫代理類,如果我們有其他類還想使用代理那就必須再寫一個代理類。然而在實際開發中我們是可能是有非常多的類是需要被代理的,並且事先我們可能並不知道我們要代理哪個類。所以如果繼續使用靜態代理反而會增加許多的工作量,並且效率低下,代碼複用率也不好。由此我們JDK爲我們提供了動態代理這個概念,即利用一個代理類就可以實現所有被代理的操作。下面我們就來詳細分析動態代理到底是怎麼一回事。

2.動態代理

2.1什麼是動態代理

之前我們瞭解到靜態代理指代理類在程序運行前就已經存在。那麼反之動態代理的代理類在的程序運行前是不存在的,也就是說代理類在程序運行時才創建的代理模式成爲動態代理。這種情況下,代理類並不是在Java代碼中定義好的,而是在程序運行時根據我們的在Java代碼中的“指示”動態生成的。這麼說比較抽象,我們先通過一個案例來看動態代理到底怎麼實現的。

2.2.動態代理簡單實現

   還是我們之前那個實例:一個班上的同學要向老師提交作業,但是老師並不直接收每個人的作業,都是通過把作業交給學習委員,再由學習委員將作業轉交給老師。我們今天用動態代理的方式來將它實現。我們沿用之前寫好的Person接口與Student類,具體代碼實現請參考上一篇博文 Java代理模式實現與詳解(一)

 我們先來寫一個代理類MyProxy,通過這個類實現動態代理。代碼如下:

大家可以看到,這個類實現了一個接口 InvocationHandler,還引入了泛型,並且重寫了invoke()方法,在invoke()方法裏我們打印了一句話記錄我們具體代理執行哪個方法。那爲什麼要這麼做呢?我們下面會對其進行詳細的介紹。

代理類我們已經寫好了,那麼我們來看看怎麼實現動態代理呢。編寫一個測試類Test.java 用於測試我們的動態代理。

運行結果:

從代碼中我們可以看到,我們先是創建一個被代理的對象,然後創建一個InvocationHandler對象handler並且將要代理的對象與其綁定。接着我們創建了一個代理對象stuProxy,並將handler作爲參數傳了去,最終通過stuProxy代理執行了submitHomework方法,完成了代理。

在代理類中我們並沒有聲明我們要代理的類具體是哪個而是在我們的測試類中,也就是我們需要用的時候才創建我們想要代理的被代理對象,並將其傳進代理類中,最終完成了代理,這就是動態代理模式。

在代理類中我們實現了一個InvocationHandler接口,在測試類中我們調用了一個Proxy類,由此我們才完成了動態代理功能,那爲什麼我們要這麼做呢?他們又代表着什麼呢?我們接下來詳細的分析其原理。

3 動態代理原理分析

3.1.InvocationHandler接口

如果想要實現動態代理模式,那麼首先必須要觀察一個接口:

java.lang.reflect.InvocationHandler;我們進到這個接口的源碼去觀察:

在這個接口裏面我們可以看到它只有一個invoke()方法。那這個invoke()方法有什麼作用呢?這個方法裏面有三個參數,那這三個參數又分別代表什麼?我們來看它裏面的註釋:

這個接口裏面對三個參數的作用做了詳細的解釋。我們總結一下:

Object proxy:表示代理類的對象。

Method method表示正在調用的方法。

Object[] args:表示接口方法裏面接收的參數,如果接口方法不使用參數則爲null

我們再來看他的返回值類型是什麼:

通過給出的註釋我們可以知道此方法返回的值一定是相應基本包裝對象類的實例。也就是返回我們的代理對象,所以這個方法就屬於代理類中調用真實主題類即被代理類的操作方法。但是我們可以發現這個方法裏沒有所對應的被代理對象,所以在創建這個類對象的時候設置好真實的操作對象。那我們又怎麼去進行設置呢?其實在上面的案例中我們已經做出了示範操作。我們要想找到代理對象則要使用java.lang.reflect.Proxy類進行動態創建。這個類是怎麼完成創建的呢?我們根據其源碼來詳細分析。

3.2. java.lang.reflect.Proxy

在這個類中我們主要關注一個方法:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException   ; 返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。正是此方法實現了我們的動態代理功能。我們先來看一下此方法裏的參數:

ClassLoader loader指的取得對象的加載器。

Class<?>[] interfaces代理設計模式的核心是圍繞接口進行的,我們必須取出全部的接口。所以這個參數指代理類要實現的接口列表。

InvocationHandler h指派方法調用的調用處理程序即代理的實現類。

我們來看這個方法具體的實現代碼:

我們先來分析上面的部分代碼,代碼中先檢查了代理的實現類對象是否爲空,是的話就拋出NullPointerException,接着對傳入的接口進行安全檢查。我們繼續來看代碼:

這裏我們根據給出的註釋我們便能明白其作用,作用是查找或者生成指定的代理類,這段代碼便是實現動態代理的核心方法,動態代理的思路其實就是生成一個新類。

我們接着往下看:

同樣的我們先看代碼註釋,意爲:使用指定的調用處理程序調用其構造函數,即一個指定接口的代理類實例。至此我們便能明白我們爲什麼要用此方法區設置我們上面所說的真實的操作對象。我們返回來看之前我們寫的案例代碼:

我們正是通過我們上面所說的Proxy.newProxyInstance方法,設置真實的操作對象,完成動態代理。方法的第一個參數我們通過類加載器取得我們要代理的對象,第二個參數我們取得了代理類要實現的接口列表也就是Person這個類裏面的接口列表,最後第三個參數我們將代理的實現類對象傳了進去。通過此方法我們獲得了一個Person的代理對象。最終通過代理對象完成了代理操作。

4 總結

本文我們對Java動態代理做了詳細的分析,並實現了一個簡單的動態代理案例。我們可以發現要實現動態代理最關鍵的就是一個接口InvocationHandler和一個類Proxy。上面動態代理的例子,其實就是一個AOP的簡單實現了。Spring的AOP實現也用到了Proxy和InvocationHandler這兩個東西。但是我們從Proxy類可以發現,本文我們分析的Java動態代理只能對接口實現代理,無法實現對class的動態代理。那是否意味着如果沒有接口我們就不能使用動態代理模式了呢?有沒有什麼代理方式能不依賴接口進行代理呢?

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章