標籤:
本文轉載+個人理解
一、引言
在學習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類型的介面,為其聲明了兩個方法:
1 public interface Subject2 {3 public void rent();4 5 public void hello(String str);6 }
接著,定義了一個類來實現這個介面,這個類就是我們的真實對象,RealSubject類:
1 public class RealSubject implements Subject 2 { 3 @Override 4 public void rent() 5 { 6 System.out.println("I want to rent my house"); 7 } 8 9 @Override10 public void hello(String str)11 {12 System.out.println("hello: " + str);13 }14 }
下一步,我們就要定義一個動態代理類了,前面說個,每一個動態代理類都必須要實現 InvocationHandler 這個介面,因此我們這個動態代理類也不例外:
1 public class DynamicProxy implements InvocationHandler 2 { 3 // 這個就是我們要代理的真實對象 4 private Object subject; 5 6 // 構造方法,給我們要代理的真實對象賦初值 7 public DynamicProxy(Object subject) 8 { 9 this.subject = subject;10 }11 12 @Override13 public Object invoke(Object object, Method method, Object[] args)14 throws Throwable15 {16 // 在代理真實對象前我們可以添加一些自己的操作17 System.out.println("before rent house");18 19 System.out.println("Method:" + method);20 21 // 當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用22 method.invoke(subject, args);23 24 // 在代理真實對象後我們也可以添加一些自己的操作25 System.out.println("after rent house"); 27 return null;28 }29 30 }
最後,來看看我們的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 housebefore 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類型的對象?原 因就是在newProxyInstance這個方法的第二個參數上,我們給這個代理對象提供了一組什麼介面,那麼我這個代理對象就會實現了這組介面,這個 時候我們當然可以將這個代理對象強制類型轉化為這組介面中的任意一個,因為這裡的介面是Subject類型,所以就可以將其轉化為Subject類型了。但是如果被代理的對象沒有實現任何的介面,該如何處理???? ---源碼中解釋: interfaces the list of interfaces for the proxy class to implement.
同時我們一定要記住,通過 Proxy.newProxyInstance 建立的代理對象是在jvm運行時動態產生的一個對象,它並不是我們的InvocationHandler類型,也不是我們定義的那組介面的類型,而是在運 行是動態產生的一個對象,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示對象的標號。
這樣一來,通過產生的新的代理對象調用被代理對象的方法,
return a proxy instance with the specified invocation handler of a proxy class that is defined by the specified class loader and that implements the specified interfaces。
四、動態代理的作用
1、方法增強,從上面的執行個體中我們可以看出在執行被代理對象的方法前後我們都可以執行一些我們想要的動作,來增強原來的方法。
2、用作遠程調用,比如現在有Java介面,這個介面的實現部署在其它伺服器上,在編寫用戶端代碼的時候,沒辦法直接調用介面方法,因為介面是不能直接產生對象的,這個時候就可以考慮代理模式(動態代理)了,通過Proxy.newProxyInstance代理一個該介面對應的InvocationHandler對象,然後在InvocationHandler的invoke方法內封裝通訊細節就可以了。具體的應用,最經典的當然是Java標準庫的RMI。
瞭解更多
五、補充
1、在Procxy中的靜態方法:
// 方法 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)
2、Proxy 靜態方法產生動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其位元組碼是由 JVM 在運行時動態產生的而非預存在於任何一個 .class 檔案中。每次產生動態代理類對象時都需要指定一個類裝載器對象,就是上面方法4的第一個參數,由他來對產生的動態代理對象進行載入。
Java--動態代理