java動態代理機制中有兩個重要的類和接口InvocationHandler(接口)和Proxy(類),這一個類Proxy和接口InvocationHandler是我們實現動態代理的核心;
1.InvocationHandler接口是proxy代理實例的調用處理程序實現的一個接口,每一個proxy代理實例都有一個關聯的調用處理程序;在代理實例調用方法時,方法調用被編碼分派到調用處理程序的invoke方法。
看下官方文檔對InvocationHandler接口的描述:
{@code InvocationHandler} is the interface implemented by the <i>invocation handler</i> of a proxy instance. <p>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 {@code invoke} method of its invocation handler.
每一個動態代理類的調用處理程序都必須實現InvocationHandler接口,並且每個代理類的實例都關聯到了實現該接口的動態代理類調用處理程序中,當我們通過動態代理對象調用一個方法時候,這個方法的調用就會被轉發到實現InvocationHandler接口類的invoke方法來調用,看如下invoke方法:
/** * proxy:代理類代理的真實代理對象com.sun.proxy.$Proxy0 * method:我們所要調用某個對象真實的方法的Method對象 * args:指代代理對象方法傳遞的參數 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
2.Proxy類就是用來創建一個代理對象的類,它提供了很多方法,但是我們最常用的是newProxyInstance方法。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Returns an instance of a proxy class for the specified interfaces
that dispatches method invocations to the specified invocation
handler. This method is equivalent to:
這個方法的作用就是創建一個代理類對象,它接收三個參數,我們來看下幾個參數的含義:
loader:一個classloader對象,定義了由哪個classloader對象對生成的代理類進行加載
interfaces:一個interface對象數組,表示我們將要給我們的代理對象提供一組什么樣的接口,如果我們提供了這樣一個接口對象數組,那么也就是聲明了代理類實現了這些接口,代理類就可以調用接口中聲明的所有方法。
h:一個InvocationHandler對象,表示的是當動態代理對象調用方法的時候會關聯到哪一個InvocationHandler對象上,並最終由其調用。
3動態代理中核心的兩個接口和類上面已經介紹完了,接下來我們就用實例來講解下具體的用法
首先我們定義一個接口People
package reflect; public interface People { public String work(); }
定義一個Teacher類,實現People接口,這個類是真實的對象
package reflect; public class Teacher implements People{ @Override public String work() { System.out.println("老師教書育人..."); return "教書"; } }
現在我們要定義一個代理類的調用處理程序,每個代理類的調用處理程序都必須實現InvocationHandler接口,代理類如下:
package reflect; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class WorkHandler implements InvocationHandler{ //代理類中的真實對象 private Object obj; public WorkHandler() { // TODO Auto-generated constructor stub } //構造函數,給我們的真實對象賦值 public WorkHandler(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在真實的對象執行之前我們可以添加自己的操作 System.out.println("before invoke。。。"); Object invoke = method.invoke(obj, args); //在真實的對象執行之后我們可以添加自己的操作 System.out.println("after invoke。。。"); return invoke; } }
上面的代理類的調用處理程序的invoke方法中的第一個參數proxy好像我們從來沒有用過,而且關於這個參數的具體用法含義請參考我的另外一篇文章Java中InvocationHandler接口中第一個參數proxy詳解
接下來我們看下客戶端類
package reflect; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { //要代理的真實對象 People people = new Teacher(); //代理對象的調用處理程序,我們將要代理的真實對象傳入代理對象的調用處理的構造函數中,最終代理對象的調用處理程序會調用真實對象的方法 InvocationHandler handler = new WorkHandler(people); /** * 通過Proxy類的newProxyInstance方法創建代理對象,我們來看下方法中的參數 * 第一個參數:people.getClass().getClassLoader(),使用handler對象的classloader對象來加載我們的代理對象 * 第二個參數:people.getClass().getInterfaces(),這里為代理類提供的接口是真實對象實現的接口,這樣代理對象就能像真實對象一樣調用接口中的所有方法 * 第三個參數:handler,我們將代理對象關聯到上面的InvocationHandler對象上 */ People proxy = (People)Proxy.newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler); //System.out.println(proxy.toString()); System.out.println(proxy.work()); } }
看下輸出結果:
before invoke。。。
老師教書育人...
after invoke。。。
教書
通過上面的講解和示例動態代理的原理及使用方法,在Spring中的兩大核心IOC和AOP中的AOP(面向切面編程)的思想就是動態代理,在代理類的前面和后面加上不同的切面組成面向切面編程。
上面我們只講解了Proxy中的newProxyInstance(生成代理類的方法),但是它還有其它的幾個方法,我們下面就介紹一下:
- getInvocationHandler:返回指定代理實例的調用處理程序
- getProxyClass:給定類加載器和接口數組的代理類的java.lang.Class對象。
- isProxyClass:當且僅當使用getProxyClass方法或newProxyInstance方法將指定的類動態生成為代理類時,才返回true。
- newProxyInstance:返回指定接口的代理類的實例,該接口將方法調用分派給指定的調用處理程序。
補充:
上一篇文章我們詳細的講解了創建代理類的調用處理程序(實現InvocationHandler接口的類),獲得代理對象的Proxy類,但是就發現InvocationHandler中的invoke方法中的第一個參數proxy好像從來沒有用過,所以就開始在網上查詢proxy的用途,最后在國外的網站上找到了不錯的講解stackoverflow.com,下面就根據自己的學習心得,講解一下proxy。
1.講解前我們先列一下我們要說明的問題
- proxy代表什么意思
- proxy參數怎么用及什么時候用
- proxy參數運行時的類型是什么
- 為什么不用this代替proxy
2.proxy代表什么意思
proxy是真實對象的真實代理對象,invoke方法可以返回調用代理對象方法的返回結果,也可以返回對象的真實代理對象(com.sun.proxy.$Proxy0)。
3.proxy參數怎么用及什么時候用
proxy參數是invoke方法的第一個參數,通常情況下我們都是返回真實對象方法的返回結果,但是我們也可以將proxy返回,proxy是真實對象的真實代理對象,我們可以通過這個返回對象對真實的對象做各種各樣的操作。
- 創建一個接口People,包含一個work方法,方法的返回對象是它本身
package com.test.Application; public interface People { public People work(String workName); public String time(); }
- 創建一個接口People的實現類Student
package com.test.Application; public class Student implements People{ @Override public People work(String workName) { System.out.println("工作內容是"+workName); return this; } @Override public String time() { return "2018-06-12"; } }
- 創建一個代理類的調用處理程序WorkHandler
package com.test.Application; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class WorkHandler implements InvocationHandler{ private Object obj; public WorkHandler(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before 動態代理..."); System.out.println(proxy.getClass().getName()); System.out.println(this.obj.getClass().getName()); if(method.getName().equals("work")) { method.invoke(this.obj, args); System.out.println("after 動態代理..."); return proxy; } else { System.out.println("after 動態代理..."); return method.invoke(this.obj, args); } } }
我們可以看到上面的代理類調用處理程序打印了proxy參數對象,並且返回了proxy對象。
- 客戶端實例創建代理對象並輸出結果
package com.test.Application; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { People people = new Student(); InvocationHandler handler = new WorkHandler(people); People proxy = (People)Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler); People p = proxy.work("寫代碼").work("開會").work("上課"); System.out.println("打印返回的對象"); System.out.println(p.getClass()); String time = proxy.time(); System.out.println(time); } }
運行結果:
after 動態代理... before 動態代理... com.sun.proxy.$Proxy0 com.test.Application.Student 工作內容是上課 after 動態代理... 打印返回的對象 class com.sun.proxy.$Proxy0 class com.sun.proxy.$Proxy0 before 動態代理... com.sun.proxy.$Proxy0 com.test.Application.Student after 動態代理... 2018-06-12
我們可以看到WorkHandler代理調用處理程序打印proxy參數輸出的結果是com.sun.proxy.$Proxy0,這也說明proxy參數是代理類的真實代理對象;Proxy類生成的代理對象可以調用work方法並且返回真實的代理對象,也可以通過反射來對真實的代理對象進行操作。
4.proxy參數運行時的類型是什么
上面我們已經打印出了proxy的類型是:com.sun.proxy.$Proxy0真實的代理對象
5.為什么不用this替代
因為this代表的是InvocationHandler接口實現類本身,並不是真實的代理對象。