動態代理是反射的一個非常重要的應用場景。動態代理常被用於一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基於 Java 動態代理實現的。
動態代理的方式有兩種:
- JDK動態代理:利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
- CGLIB動態代理:利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。
JDK動態代理和CGLIB字節碼生成的區別?
- JDK動態代理只能對實現了接口的類生成代理,而不能針對類。
- CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法。
因為是繼承,所以該類或方法最好不要聲明成final 。
一、靜態代理
了解動態代理之前,我們先來看看靜態代理。
靜態代理其實就是指設計模式中的代理模式。
代理模式為其他對象提供一種代理以控制對這個對象的訪問。
Subject 定義了 RealSubject 和 Proxy 的公共接口,這樣就在任何使用 RealSubject 的地方都可以使用 Proxy 。
abstract class Subject { public abstract void Request(); }
RealSubject 定義 Proxy 所代表的真實實體。
class RealSubject extends Subject { @Override public void Request() { System.out.println("真實的請求"); } }
Proxy 保存一個引用使得代理可以訪問實體,並提供一個與 Subject 的接口相同的接口,這樣代理就可以用來替代實體。
class Proxy extends Subject { private RealSubject real; @Override public void Request() { if (null == real) { real = new RealSubject(); } real.Request(); } }
靜態代理模式固然在訪問無法訪問的資源,增強現有的接口業務功能方面有很大的優點,但是大量使用這種靜態代理,會使我們系統內的類的規模增大,並且不易維護;並且由於 Proxy 和 RealSubject 的功能本質上是相同的,Proxy 只是起到了中介的作用,這種代理在系統中的存在,導致系統結構比較臃腫和松散。
二、JDK動態代理
為了解決靜態代理的問題,就有了創建動態代理的想法:
在運行狀態中,需要代理的地方,根據 Subject 和 RealSubject,動態地創建一個 Proxy,用完之后,就會銷毀,這樣就可以避免了 Proxy 角色的 class 在系統中冗雜的問題了。
Java 動態代理基於經典代理模式,引入了一個 InvocationHandler,InvocationHandler 負責統一管理所有的方法調用。
動態代理步驟:
- 獲取 RealSubject 上的所有接口列表;
- 確定要生成的代理類的類名,默認為:
com.sun.proxy.$ProxyXXXX
; - 根據需要實現的接口信息,在代碼中動態創建 該 Proxy 類的字節碼;
- 將對應的字節碼轉換為對應的 class 對象;
- 創建
InvocationHandler
實例 handler,用來處理Proxy
所有方法調用; - Proxy 的 class 對象 以創建的 handler 對象為參數,實例化一個 proxy 對象。
從上面可以看出,JDK 動態代理的實現是基於實現接口的方式,使得 Proxy 和 RealSubject 具有相同的功能。
在 Java 的動態代理機制中,有兩個重要的類(接口),一個是 InvocationHandler
接口、另一個則是 Proxy
類,這一個類和一個接口是實現我們動態代理所必須用到的。
1. InvocationHandler 接口
InvocationHandler
接口定義:
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
每一個動態代理類都必須要實現 InvocationHandler
這個接口,並且每個代理類的實例都關聯到了一個 Handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由 InvocationHandler
這個接口的 invoke
方法來進行調用。
我們來看看 InvocationHandler 這個接口的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
參數說明:
- proxy - 代理的真實對象。
- method - 所要調用真實對象的某個方法的
Method
對象 - args - 所要調用真實對象某個方法時接受的參數
2. Proxy 類
Proxy
這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance
這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
這個方法的作用就是得到一個動態的代理對象。
參數說明:
- loader - 一個 ClassLoader 對象,定義了由哪個 ClassLoader 對象來對生成的代理對象進行加載。
- interfaces - 一個 Interface 對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了
- h - 一個 InvocationHandler 對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個 InvocationHandler 對象上
3. 動態代理示例
首先我們定義了一個 Subject 類型的接口,為其聲明了兩個方法:
public interface Subject { void hello(String str); String bye(); }
接着,定義了一個類來實現這個接口,這個類就是我們的真實對象,RealSubject 類:
public class RealSubject implements Subject { @Override public void hello(String str) { System.out.println("Hello " + str); } @Override public String bye() { System.out.println("Goodbye"); return "Over"; } }
下一步,我們就要定義一個動態代理類了,前面說個,每一個動態代理類都必須要實現 InvocationHandler 這個接口,因此我們這個動態代理類也不例外:
public class InvocationHandlerDemo implements InvocationHandler { // 這個就是我們要代理的真實對象 private Object subject; // 構造方法,給我們要代理的真實對象賦初值 public InvocationHandlerDemo(Object subject) { this.subject = subject; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { // 在代理真實對象前我們可以添加一些自己的操作 System.out.println("Before method"); System.out.println("Call Method: " + method); // 當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用 Object obj = method.invoke(subject, args); // 在代理真實對象后我們也可以添加一些自己的操作 System.out.println("After method"); System.out.println(); return obj; } }
最后,來看看我們的 Client 類:
public class Client { public static void main(String[] args) { // 我們要代理的真實對象 Subject realSubject = new RealSubject(); // 我們要代理哪個真實對象,就將該對象傳進去,最后是通過該真實對象來調用其方法的 InvocationHandler handler = new InvocationHandlerDemo(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.hello("World"); String result = subject.bye(); System.out.println("Result is: " + result); } }
控制台輸出:
com.sun.proxy.$Proxy0 Before method Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String) Hello World After method Before method Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye() Goodbye After method Result is: Over
我們首先來看看 com.sun.proxy.$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.hello("World");
String result = subject.bye();
這里是通過代理對象來調用實現的那種接口中的方法,這個時候程序就會跳轉到由這個代理對象關聯到的 handler 中的 invoke 方法去執行,而我們的這個 handler 對象又接受了一個 RealSubject 類型的參數,表示我要代理的就是這個真實對象,所以此時就會調用 handler 中的 invoke 方法去執行。
我們看到,在真正通過代理對象來調用真實對象的方法的時候,我們可以在該方法前后添加自己的一些操作,同時我們看到我們的這個 method 對象是這樣的:
public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String) public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
正好就是我們的 Subject 接口中的兩個方法,這也就證明了當我通過代理對象來調用方法的時候,起實際就是委托由其關聯到的 handler 對象的 invoke 方法中來調用,並不是自己來真實調用,而是通過代理的方式來調用的。
三、CGLIB動態代理
1. 添加依賴
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>
目標類(一個公開方法,另外一個用final修飾):
public class Dog{ final public void run(String name) { System.out.println("狗"+name+"----run"); } public void eat() { System.out.println("狗----eat"); } }
方法攔截器
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("這里是對目標類進行增強!!!"); //注意這里的方法調用,不是用反射哦!!! Object object = proxy.invokeSuper(obj, args); return object; } }
測試
public class CgLibProxy { public static void main(String[] args) { //在指定目錄下生成動態代理類,我們可以反編譯看一下里面到底是一些什么東西 //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workspace"); //創建Enhancer對象,類似於JDK動態代理的Proxy類,下一步就是設置幾個參數 Enhancer enhancer = new Enhancer(); //設置目標類的字節碼文件 enhancer.setSuperclass(Dog.class); //設置回調函數 enhancer.setCallback(new MyMethodInterceptor()); //這里的creat方法就是正式創建代理類 Dog proxyDog = (Dog)enhancer.create(); //調用代理類的eat方法 proxyDog.eat(); } }
四、小結
參考
https://www.cnblogs.com/jingmoxukong/p/12049112.html