標籤:
轉載自http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
代理模式
代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預先處理訊息、過濾訊息、把訊息轉寄給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。
按照代理的建立時期,代理類可以分為兩種。
靜態代理:由程式員建立或特定工具自動產生原始碼,再對其編譯。在程式運行前,代理類的.class檔案就已經存在了。
動態代理:在程式運行時,運用反射機制動態建立而成。
首先看一下靜態代理:
1、Count.java
- package net.battier.dao;
-
- /**
- * 定義一個賬戶介面
- *
- * @author Administrator
- *
- */
- public interface Count {
- // 查看賬戶方法
- public void queryCount();
-
- // 修改賬戶方法
- public void updateCount();
-
- }
2、CountImpl.java
- package net.battier.dao.impl;
-
- import net.battier.dao.Count;
-
- /**
- * 委託類(包含商務邏輯)
- *
- * @author Administrator
- *
- */
- public class CountImpl implements Count {
-
- @Override
- public void queryCount() {
- System.out.println("查看賬戶方法...");
-
- }
-
- @Override
- public void updateCount() {
- System.out.println("修改賬戶方法...");
-
- }
-
- }
-
- 、CountProxy.java
- package net.battier.dao.impl;
-
- import net.battier.dao.Count;
-
- /**
- * 這是一個代理類(增強CountImpl實作類別)
- *
- * @author Administrator
- *
- */
- public class CountProxy implements Count {
- private CountImpl countImpl;
-
- /**
- * 覆蓋預設構造器
- *
- * @param countImpl
- */
- public CountProxy(CountImpl countImpl) {
- this.countImpl = countImpl;
- }
-
- @Override
- public void queryCount() {
- System.out.println("交易處理之前");
- // 調用委託類的方法;
- countImpl.queryCount();
- System.out.println("交易處理之後");
- }
-
- @Override
- public void updateCount() {
- System.out.println("交易處理之前");
- // 調用委託類的方法;
- countImpl.updateCount();
- System.out.println("交易處理之後");
-
- }
-
- }
3、TestCount.java
- package net.battier.test;
-
- import net.battier.dao.impl.CountImpl;
- import net.battier.dao.impl.CountProxy;
-
- /**
- *測試Count類
- *
- * @author Administrator
- *
- */
- public class TestCount {
- public static void main(String[] args) {
- CountImpl countImpl = new CountImpl();
- CountProxy countProxy = new CountProxy(countImpl);
- countProxy.updateCount();
- countProxy.queryCount();
-
- }
- }
觀察代碼可以發現每一個代理類只能為一個介面服務,這樣一來程式開發中必然會產生過多的代理,而且,所有的代理操作除了調用的方法不一樣之外,其他的操作都一樣,則此時肯定是重複代碼。解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能,那麼此時就必須使用動態代理完成。
在學習Spring的時候,我們知道Spring主要有兩大思想,一個是IoC,另一個就是AOP,對於IoC,依賴注入就不用多說了,而對於Spring的核心AOP來說,我們不但要知道怎麼通過AOP來滿足的我們的功能,我們更需要學習的是其底層是怎麼樣的一個原理,而AOP的原理就是java的動態代理機制,所以本篇隨筆就是對java的動態機制進行一個回顧。
在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
我們看到這個方法一共接受三個參數,那麼這三個參數分別代表什麼呢?
Object invoke(Object proxy, Method method, Object[] args) throws Throwableproxy: 指代我們所代理的那個真實對象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這個類的作用就是用來動態建立一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是 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.
這個方法的作用就是得到一個動態代理對象,其接收三個參數,我們來看看這三個參數所代表的含義:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentExceptionloader: 一個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; } @Override public 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類:
public class Client{ public static void main(String[] args) { // 我們要代理的真實對象 Subject realSubject = new RealSubject(); // 我們要代理哪個真實對象,就將該對象傳進去,最後是通過該真實對象來調用其方法的 InvocationHandler handler = new DynamicProxy(realSubject); /* * 通過Proxy的newProxyInstance方法來建立我們的代理對象,我們來看看其三個參數 * 第一個參數 handler.getClass().getClassLoader() ,我們這裡使用handler這個類的ClassLoader對象來載入我們的代理對象 * 第二個參數realSubject.getClass().getInterfaces(),我們這裡為代理對象提供的介面是真實對象所實行的介面,表示我要代理的是該真實對象,這樣我就能調用這組介面中的方法了 * 第三個參數handler, 我們這裡將這個代理對象關聯到了上方的 InvocationHandler 這個對象上 */ 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 houseMethod:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()I want to rent my houseafter rent house
before rent houseMethod:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)hello: worldafter rent house
我們首先來看看 $Proxy0 這東西,我們看到,這個東西是由 System.out.println(subject.getClass().getName()); 這條語句列印出來的,那麼為什麼我們返回的這個代理對象的類名是這樣的呢?
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler);
可能我以為返回的這個代理對象會是Subject類型的對象,或者是InvocationHandler的對象,結果卻不是,首先我們解釋一下為什麼我們這裡可以將其轉化為Subject類型的對象?原因就是在newProxyInstance這個方法的第二個參數上,我們給這個代理對象提供了一組什麼介面,那麼我這個代理對象就會實現了這組介面,這個時候我們當然可以將這個代理對象強制類型轉化為這組介面中的任意一個,因為這裡的介面是Subject類型,所以就可以將其轉化為Subject類型了。
同時我們一定要記住,通過 Proxy.newProxyInstance 建立的代理對象是在jvm運行時動態產生的一個對象,它並不是我們的InvocationHandler類型,也不是我們定義的那組介面的類型,而是在運行是動態產生的一個對象,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示對象的標號。
接著我們來看看這兩句
subject.rent();
subject.hello("world");
這裡是通過代理對象來調用實現的那種介面中的方法,這個時候程式就會跳轉到由這個代理對象關聯到的 handler 中的invoke方法去執行,而我們的這個 handler 對象又接受了一個 RealSubject類型的參數,表示我要代理的就是這個真實對象,所以此時就會調用 handler 中的invoke方法去執行:
public 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; }
我們看到,在真正通過代理對象來調用真實對象的方法的時候,我們可以在該方法前後添加自己的一些操作,同時我們看到我們的這個 method 對象是這樣的:
public abstract void com.xiaoluo.dynamicproxy.Subject.rent()public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
正好就是我們的Subject介面中的兩個方法,這也就證明了當我通過代理對象來調用方法的時候,起實際就是委託由其關聯到的 handler 對象的invoke方法中來調用,並不是自己來真實調用,而是通過代理的方式來調用的。
這就是我們的java動態代理機制
本篇隨筆詳細的講解了java中的動態代理機制,這個知識點非常非常的重要,包括我們Spring的AOP其就是通過動態代理的機制實現的,所以我們必須要好好的理解動態代理的機制。
總結
一個典型的動態代理建立對象過程可分為以下四個步驟:
1、通過實現InvocationHandler介面建立自己的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通過為Proxy類指定ClassLoader對象和一組interface建立動態代理類
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通過反射機制擷取動態代理類的建構函式,其參數類型是調用處理器介面類型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通過建構函式建立代理類執行個體,此時需將調用處理器對象作為參數被傳入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
為了簡化對象建立過程,Proxy類中的newInstance方法封裝了2~4,只需兩步即可完成代理對象的建立。
產生的ProxySubject繼承Proxy類實現Subject介面,實現的Subject的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的的方法(Object result=method.invoke(proxied,args))
美中不足
誠然,Proxy已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援interface代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態產生的代理類的繼承關係圖,它們已經註定有一個共同的父類叫Proxy。Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對 class代理的必要性,但是同樣有一些理由,相信支援class動態代理會更美好。介面和類的劃分,本就不是很明顯,只是到了Java中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類別。實現對抽象類別的動態代理,相信也有其內在的價值。此外,還有一些曆史遺留的類,它們將因為沒有實現任何介面而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。但是,不完美並不等於不偉大,偉大是一種本質,Java動態代理就是佐例。
[轉載] java的動態代理機制詳解