java動態代理實現與原理詳細分析

關於Java中的動態代理,我們首先需要了解的是一種常用的設計模式--代理模式,而對於代理,根據創建代理類的時間點,又可以分爲靜態代理和動態代理。

 

一、代理模式

代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。

簡單的說就是,我們在訪問實際對象時,是通過代理對象來訪問的,代理模式就是在訪問實際對象時引入一定程度的間接性,因爲這種間接性,可以附加多種用途。在後面我會解釋這種間接性帶來的好處。代理模式結構圖(圖片來自《大話設計模式》):

 

二、靜態代理

1、靜態代理

靜態代理:由程序員創建或特定工具自動生成源代碼,也就是在編譯時就已經將接口,被代理類,代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成。

2、靜態代理簡單實現

根據上面代理模式的類圖,來寫一個簡單的靜態代理的例子。我這兒舉一個比較粗糙的例子,假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這裏,班長就是代理學生上交班費,班長就是學生的代理。

首先,我們創建一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有上交班費的行爲。這樣,學生上交班費就可以讓班長來代理執行。

Student類實現Person接口。Student可以具體實施上交班費的動作。

StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,由於實現了Peson接口,同時持有一個學生對象,那麼他可以代理學生類對象執行上交班費(執行giveMoney()方法)行爲。

下面測試一下,看如何使用代理模式:

 

運行結果:

 

這裏並沒有直接通過張三(被代理對象)來執行上交班費的行爲,而是通過班長(代理對象)來代理執行了。這就是代理模式。

 

代理模式最主要的就是有一個公共接口(Person),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的實例,代爲執行具體類實例方法。上面說到,代理模式就是在訪問實際對象時引入一定程度的間接性,因爲這種間接性,可以附加多種用途。這裏的間接性就是指不直接調用實際對象的方法,那麼我們在代理過程中就可以加上一些其他用途。

就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,通過代理模式很輕鬆就能辦到:

運行結果:

 

可以看到,只需要在代理類中幫張三上交班費之前,執行其他操作就可以了。這種操作,也是使用代理模式的一個很大的優點。最直白的就是在Spring中的面向切面編程(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。

 

三、動態代理

1.動態代理

代理類在程序運行時創建的代理方式被成爲動態代理。我們上面靜態代理的例子中,代理類(studentProxy)是自己定義好的,在程序運行之前就已經編譯完成。然而動態代理,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。

相比於靜態代理,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。比如說,想要在每個代理的方法前都加上一個處理方法:

這裏只有一個giveMoney方法,就寫一次beforeMethod方法,但是如果出了giveMonney還有很多其他的方法,那就需要寫很多次beforeMethod方法,麻煩。那看看下面動態代理如何實現。

 

2、動態代理簡單實現

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態代理類和動態代理對象。

創建一個動態代理對象步驟,具體代碼見後面:

 

  • 創建一個InvocationHandler對象

//創建一個與代理對象相關聯的InvocationHandler
InvocationHandlerstuHandler=newMyInvocationHandler<Person>(stu);

 

  • 使用Proxy類的getProxyClass靜態方法生成一個動態代理類stuProxyClass

Class<?>stuProxyClass=Proxy.getProxyClass(Person.class.getClassLoader(),newClass<?>[]{Person.class});

 

  • 獲得stuProxyClass中一個帶InvocationHandler參數的構造器constructor

Constructor<?>constructor=PersonProxy.getConstructor(InvocationHandler.class);

 

  • 通過構造器constructor來創建一個動態實例stuProxy

PersonstuProxy=(Person)cons.newInstance(stuHandler);

 

就此,一個動態代理對象就創建完畢,當然,上面四個步驟可以通過Proxy類的newProxyInstances方法來簡化:

 

到這裏肯定都會很疑惑,這動態代理到底是如何執行的,是如何通過代理對象來執行被代理對象的方法的,先不急,我們先看看一個簡單的完整的動態代理的例子。還是上面靜態代理的例子,班長需要幫學生代交班費。

首先是定義一個Person接口:

 

創建需要被代理的實際類:

 

再定義一個檢測方法執行時間的工具類,在任何方法執行前先調用start方法,執行後調用finsh方法,就可以計算出該方法的運行時間,這也是一個最簡單的方法執行時間檢測工具。

創建StuInvocationHandler類,實現InvocationHandler接口,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執行代理對象的方法都會被替換成執行invoke方法。

 

再再invoke方法中執行被代理對象target的相應方法。當然,在代理過程中,我們在真正執行被代理對象的方法前加入自己其他處理。這也是Spring中的AOP實現的主要原理,這裏還涉及到一個很重要的關於java反射方面的基礎知識。

做完上面的工作後,我們就可以具體來創建動態代理對象了,上面簡單介紹瞭如何創建動態代理對象,我們使用簡化的方式創建動態代理對象:

 

我們執行這個ProxyTest類,先想一下,我們創建了一個需要被代理的學生張三,將zhangsan對象傳給了stuHandler中,我們在創建代理對象stuProxy時,將stuHandler作爲參數了的,上面也有說到所有執行代理對象的方法都會被替換成執行invoke方法,也就是說,最後執行的是StuInvocationHandler中的invoke方法。所以在看到下面的運行結果也就理所當然了。

運行結果:

 

上面說到,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因爲所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。例如,這裏的方法計時,所有的被代理對象執行的方法都會被計時,然而我只做了很少的代碼量。

 

動態代理的過程,代理對象和被代理對象的關係不像靜態代理那樣一目瞭然,清晰明瞭。因爲動態代理的過程中,我們並沒有實際看到代理類,也沒有很清晰地的看到代理類的具體樣子,而且動態代理中被代理對象和代理對象是通過InvocationHandler來完成的代理過程的,其中具體是怎樣操作的,爲什麼代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行。帶着這些問題,我們就需要對java動態代理的源碼進行簡要的分析,弄清楚其中緣由。

 

四、動態代理原理分析

1、Java動態代理創建出來的動態代理類

 

上面我們利用Proxy類的newProxyInstance方法創建了一個動態代理對象,查看該方法的源碼,發現它只是封裝了創建動態代理類的步驟(紅色標準部分):

 

其實,我們最應該關注的是Class<?>cl=getProxyClass0(loader,intfs);這句,這裏產生了代理類,後面代碼中的構造器也是通過這裏產生的類來獲得,可以看出,這個類的產生就是整個動態代理的關鍵,由於是動態生成的類文件,我這裏不具體進入分析如何產生的這個類文件,只需要知道這個類文件時緩存在java虛擬機中的,我們可以通過下面的方法將其打印到文件裏面,一睹真容:

對這個class文件進行反編譯,我們看看jdk爲我們生成了什麼樣的內容:

jdk爲我們的生成了一個叫$Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)的代理類,這個類文件時放在內存中的,我們在創建代理對象時,就是通過反射獲得這個類的構造方法,然後創建的代理實例。通過對這個生成的代理類源碼的查看,我們很容易能看出,動態代理實現的具體過程。

我們可以對InvocationHandler看做一箇中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉爲對被代理對象的調用。

代理類調用自己方法時,通過自身持有的中介類對象來調用中介類對象的invoke方法,從而達到代理執行被代理對象的方法。也就是說,動態代理通過中介類實現了具體的代理功能。

 

五、總結

生成的代理類:$Proxy0extendsProxyimplementsPerson,我們看到代理類繼承了Proxy類,所以也就決定了java動態代理只能對接口進行代理,Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理。

上面的動態代理的例子,其實就是AOP的一個簡單實現了,在目標對象的方法執行之前和執行之後進行了處理,對方法耗時統計。Spring的AOP實現其實也是用了Proxy和InvocationHandler這兩個東西的。

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