關注微信公眾號【
Java之言】,更多干貨文章和學習資料,助你放棄編程之路!
文章目錄
- 一、代理模式
- 二、靜態代理
- 三、動態代理
- 3.1 JDK動態代理
- 3.2 Cglib動態代理
- 四、兩種動態代理區別
一、代理模式
代理模式(Proxy Pattern)是程式設計中的一種
設計模式,他的特征是代理類和委托類實作有同樣的介面,代理類主要負責為委托類預處理訊息、過濾訊息、把訊息轉發給委托類,以及事后處理訊息等,
代理類與委托類之間通常會存在關聯關系,一個代理類物件與一個委托類物件(目標物件)關聯,代理類物件本身并不真正實作服務,而是通過呼叫委托類物件的相關方法,來提供特定的服務,
即通過代理物件訪問目標物件,這樣我們可以在目標物件實作的基礎上,增強額外的功能操作,即擴展目標物件的功能,
以最近很火的貴州茅臺為例子,假如我們要買1箱茅臺,我們不是直接跟茅臺公司買的,而是通過代理商(例如沃爾瑪,京東等)進行購買,茅臺公司就是一個目標物件,它只負責生產茅臺,而其他如何銷售,寄快遞等瑣碎的事交由代理商(代理物件)來處理,

二、靜態代理
代理物件與目標物件一起實作相同的介面或者繼承相同父類,由程式員創建或特定工具自動生成源代碼,即在編譯時就已經確定了介面,目標類,代理類等,
在程式運行之前,代理類的 .class 檔案就已經生成,
你可以簡單認為代理物件寫死持有目標物件,
package com.nobody.staticproxy;
/**
* @Description 白酒廠商
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public interface WhileWineCompany {
// 生產酒 (演示才寫一個方法,實際多個方法都是能被代理的)
void product();
}
package com.nobody.staticproxy;
/**
* @Description 委托類,貴州茅臺
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Moutai implements WhileWineCompany {
public void product() {
System.out.println("生產貴州茅臺...");
}
}
package com.nobody.staticproxy;
/**
* @Description 代理類,京東代理商
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class JDProxy implements WhileWineCompany {
// 被代理的貴州茅臺公司
private Moutai moutai;
public JDProxy(Moutai moutai) {
this.moutai = moutai;
}
public void product() {
System.out.println("京東商城下訂單購買");
// 實際呼叫目標物件的方法
moutai.product();
System.out.println("京東商城發快遞");
}
}
package com.nobody.staticproxy;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
// 生成代理物件,并傳入被代理物件
WhileWineCompany proxy = new JDProxy(new Moutai());
proxy.product();
}
}
// 輸出結果
京東商城下訂單購買
生產貴州茅臺...
京東商城發快遞
靜態代理優缺點:
- 優點:在不修改目標物件的功能前提下,可以對目標功能擴展,
- 缺點:假如又有一個目標類,也要做增強,則還需要新增相對應的代理類,導致我們要手動撰寫很多代理類,同時,一旦介面增加方法,目標物件與代理物件都要維護,
三、動態代理
代理類在程式運行時才創建的代理方式被稱為動態代理,
靜態代理中,代理類(JDProxy)是我們程式員定義的,在程式運行之前就已經編譯完成,而動態代理中的代理類并不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的,
在 Java 中,有2種動態代理實作方式,JDK動態代理和CGLIB動態代理,
Spring 中的 AOP 是依靠動態代理來實作切面編程的,
3.1 JDK動態代理
JDK動態代理是基于反射機制,生成一個實作代理介面的匿名類,然后重寫方法進行方法增強,在呼叫具體方法前通過呼叫
InvokeHandler的invoke方法來處理,
它的特點是生成代理類速度很快,但是運行時呼叫方法操作會比較慢,因為是基于反射機制的,而且只能針對介面編程,即目標物件要實作介面,
如果目標物件實作了介面,默認情況下會采用JDK的動態代理,
實作JDK動態代理,我們需要借助
java.lang.reflect包下的Proxy 類和InvocationHandler介面
package com.nobody.jdkproxy;
/**
* @Description 白酒廠商
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public interface WhileWineCompany {
// 生產酒 (演示才寫一個方法,實際多個方法都是能被代理的)
void product();
}
package com.nobody.jdkproxy;
/**
* @Description 委托類,貴州茅臺
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Moutai implements WhileWineCompany {
public void product() {
System.out.println("生產貴州茅臺...");
}
}
package com.nobody.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description JDK動態代理實作InvocationHandler介面
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class JDKProxy implements InvocationHandler {
// 被代理的目標物件
private Object target;
/**
* 所有執行代理物件的方法都會被替換成執行invoke方法
*
* @param proxy 代理物件
* @param method 將要執行的方法資訊
* @param args 執行方法需要的引數
* @return 方法執行后的回傳值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("京東商城下訂單");
// 通過反射,呼叫目標物件的方法并傳入引數
Object result = method.invoke(target, args);
System.out.println("京東商城發快遞");
return result;
}
/**
* 獲取代理物件
*
* @param target 被代理的物件
* @return 代理物件
*/
public Object getJDKProxy(Object target) {
this.target = target;
// loader:ClassLoader物件,定義哪個ClassLoader物件加載生成代理物件
// interfaces:一個Interface物件的陣列,即給代理的物件提供一組介面,代理物件就會實作了這些介面(多型),這樣我們就能呼叫這些介面中的方法
// h::InvocationHandler物件,當動態代理物件在呼叫方法的時候,會呼叫InvocationHandler物件的invoke方法
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
}
package com.nobody.jdkproxy;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
JDKProxy jdkProxy = new JDKProxy();
// 獲取貴州茅臺的代理物件
WhileWineCompany proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new Moutai());
// 執行方法
proxy.product();
}
}
// 輸出結果
京東商城下訂單
生產貴州茅臺...
京東商城發快遞
如果此時我們又增加了一個目標物件,也要進行代理,我們只需要定義一個委托類實作介面即可,
package com.nobody.jdkproxy;
/**
* @Description 委托類,酒鬼酒
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class JiuGuiJiu implements WhileWineCompany {
public void product() {
System.out.println("生產酒鬼酒...");
}
}
package com.nobody.jdkproxy;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
JDKProxy jdkProxy = new JDKProxy();
// 獲取貴州茅臺的代理物件
WhileWineCompany proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new Moutai());
// 執行方法
proxy.product();
System.out.println("----------------------");
// 獲取酒鬼酒的代理物件
proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new JiuGuiJiu());
// 執行方法
proxy.product();
}
}
// 輸出結果
京東商城下訂單
生產貴州茅臺...
京東商城發快遞
----------------------
京東商城下訂單
生產酒鬼酒...
京東商城發快遞
動態代理讓我們方便對代理類的方法進行統一處理,而不用修改每個代理類中的方法,因為所有被代理執行的方法,最終都是呼叫 InvocationHandler 中的 invoke 方法,所以我們可以在 invoke 方法中統一進行增強處理,
在JDK動態代理的程序中,沒有看到實際的代理類,代理物件又是如何通過呼叫 InvocationHandler 的
invoke方法來完成代理程序的?通過JDK原始碼分析其實是 Proxy 類的 newProxyInstance
方法在運行時動態生成位元組碼生成代理類(快取在Java虛擬機記憶體中),從而創建了一個動態代理物件,
我們通過以下方法將生成的代理類列印到本地查看:
byte[] classFile =
ProxyGenerator.generateProxyClass("$Proxy0", Moutai.class.getInterfaces());
String path = "D:/$Proxy0.class";
try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}
最終,生成的class檔案通過反編譯如下,可以看出代理類繼承了 Proxy,并且實作了與目標物件同樣的 WhileWineCompany 介面,所以不難理解為什么 newProxyInstance 方法生成的代理物件能賦值給 WhileWineCompany 介面,而且從中也看出代理類的 product 方法實際呼叫了 InvocationHandler 的 invoke 方法,從而最終實作對目標物件的方法代理,
簡單可總結為,代理物件($Proxy0)持有 InvocationHandler 物件(我們定義的JDKProxy),InvocationHandler 物件持有目標物件(target),InvocationHandler 中的 invoke 方法對目標物件的方法進行增強處理,從而達到呼叫代理物件的方法,代理物件呼叫 InvocationHandler 的 invoke方法,invoke 方法中又呼叫了目標物件的方法,
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.nobody.staticproxy.WhileWineCompany;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements WhileWineCompany {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void product() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.nobody.staticproxy.WhileWineCompany").getMethod("product");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
JDK為我們的生成了一個叫$Proxy0(0是編號,有多個代理類會依次遞增)的代理類,這個類檔案資訊存放在記憶體中,我們在創建代理物件時,是通過反射獲得這個類的構造方法,然后創建的代理實體,
代理類繼承了 Proxy 類,因為在Java中是單繼承的,所以這就是為什么JDK動態代理中,目標物件一定要實作介面,
3.2 Cglib動態代理
JDK動態代理要求目標物件要實作介面,那如果一個類沒有實作介面呢?那可以用 Cglib 實作動態代理,
Cglib(Code Generation Library)是一個強大的,高性能,高質量的Code生成類別庫,它是開源的,動態代理是利用 asm 開源包,將目標物件類的 class 檔案加載進來,然后修改其位元組碼生成新的子類來進行擴展處理,即可以在運行期擴展Java類和實作Java介面,它廣泛的被許多AOP的框架使用中,例如 Spring AOP,為他們提供方法的 interception(攔截),
Cglib包的底層是通過使用一個小而快的位元組碼處理框架ASM來轉換位元組碼并生成新的類,不推薦直接使用ASM,除非你對JVM內部結構包括class檔案的格式和指令集都了如指掌,
總結為,cglib繼承被代理的類,重寫方法,織入通知,動態生成位元組碼并運行,要求被代理的類不能final修飾符修飾,
<!-- 引入cglib依賴 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
package com.nobody.cglib;
/**
* @Description 委托類,貴州茅臺
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Moutai {
public void product() {
System.out.println("生產貴州茅臺...");
}
}
package com.nobody.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @Description 動態代理類,實作MethodInterceptor方法攔截器介面
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class CglibProxy implements MethodInterceptor {
// 被代理的目標物件
private Object target;
// 動態生成一個新的類,使用父類的無參構造方法創建一個指定了特定回呼的代理實體
/**
* 動態生成一個新的類
*
* @param target
* @return
*/
public Object getProxyObject(Object target) {
this.target = target;
// 增強器,動態代碼生成器
Enhancer enhancer = new Enhancer();
// 回呼方法
enhancer.setCallback(this);
// 設定生成代理類的父型別別
enhancer.setSuperclass(target.getClass());
// 動態生成位元組碼并回傳代理物件
return enhancer.create();
}
/**
* 攔截方法
*
* @param o CGLib動態生成的代理類實體
* @param method 上文中物體類所呼叫的被代理的方法參考
* @param objects 方法引數值串列
* @param methodProxy 生成的代理類對方法的代理參考
* @return 代理物件
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("京東商城下訂單");
Object result = methodProxy.invoke(target, objects);
System.out.println("京東商城發快遞");
return result;
}
}
package com.nobody.cglib;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/12
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
// 目標物件
Moutai moutai = new Moutai();
// 代理物件
Moutai proxy = (Moutai) new CglibProxy().getProxyObject(moutai);
// 執行代理物件的方法
proxy.product();
}
}
//輸出結果
京東商城下訂單
生產貴州茅臺...
京東商城發快遞
Cglib動態代理注意的2點:
- 被代理類不能是 final 修飾的,
- 需要擴展的方法不能有 final 或 static 關鍵字修飾,不然不會被攔截,即執行方法只會執行目標物件的方法,不會執行方法擴展的內容,
四、兩種動態代理區別
JDK動態代理是基于反射機制,生成一個實作代理介面的匿名類,而Cglib動態代理是基于繼承機制,繼承被代理類,底層是基于asm第三方框架對代理物件類的class檔案加載進來,通過修改其位元組碼生成子類來處理,
JDK動態代理是生成類的速度快,后續執行類的方法操作慢;Cglib動態代理是生成類的速度慢,后續執行類的方法操作快,
JDK只能針對介面編程,Cglib可以針對類和介面,在Springboot專案中,在組態檔中增加spring.aop.proxy-target-class=true即可強制使用Cglib動態代理實作AOP,
如果目標物件實作了介面,默認情況下是采用JDK動態實作AOP,如果目標物件沒有實作介面,必須采用CGLIB庫動態實作AOP,
此演示專案已上傳到Github,如有需要可自行下載,歡迎
Star,
https://github.com/LucioChn/dynamic-proxy-demo
關注微信公眾號【
Java之言】,更多干貨文章和學習資料,助你放棄編程之路!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/259454.html
標籤:java
上一篇:1小時掌握列舉類
下一篇:JAVA基礎知識

