Java結(jié)構(gòu)型模式之代理模式詳解
一.介紹
在代理模式(Proxy Pattern)屬于結(jié)構(gòu)型模式。在代理模式中,我們對一個對象提供一個代理對象,使用代理對象控制原對象的引用,目的是為了透明的控制對象訪問
二.UML類圖

三.代理模式分類
Java中的代理按照代理類生成時機不同分為靜態(tài)代理和動態(tài)代理,靜態(tài)代理的代理類在編譯器就生成,而動態(tài)代理的代理類在Java運行時動態(tài)生成。動態(tài)代理又分為JDK代理和CGLib代理。
四.靜態(tài)代理
業(yè)務代碼
/**
* 靜態(tài)代理
*/
public interface Pay {
void pay();
}
//真實類
class Alipay implements Pay {
@Override
public void pay() {
System.out.println("支付寶支付");
}
}
//代理類
class AlipayProxy implements Pay{
//組合真實對象
private final Alipay alipay = new Alipay();
@Override
public void pay() {
long startTime = System.currentTimeMillis();
alipay.pay();
System.out.println("執(zhí)行了" + (System.currentTimeMillis()-startTime) + "毫秒"); //支付寶支付 執(zhí)行了0毫秒
}
}
測試代碼
public class Client {
public static void main(String[] args) {
new AlipayProxy().pay();
}
}
五.靜態(tài)代理的優(yōu)缺點
優(yōu)點
- 符合開閉原則
- 功能增強無需改動原業(yè)務代碼(解耦)
缺點
- 一個具體類就要產(chǎn)生一個代理類,可能會造成類爆炸
六.動態(tài)代理
為了彌補靜態(tài)代理的缺點,引入了動態(tài)代理
1.JDK動態(tài)代理(利用Java提供的代理機制)
業(yè)務代碼
/**
* JDK動態(tài)代理
*/
public interface Pay {
void pay();
}
//真實類
class Alipay implements Pay {
@Override
public void pay() {
System.out.println("支付寶支付");
}
}
class PayProxy {
//組合真實對象
private Pay pay;
public PayProxy(Pay pay) {
this.pay = pay;
}
public Pay getProxy() {
return (Pay) Proxy.newProxyInstance(getClass().getClassLoader(), pay.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(pay, args);
System.out.println("執(zhí)行了" + (System.currentTimeMillis() - startTime) + "毫秒");
return result;
}
});
}
}
測試代碼
public class Client {
public static void main(String[] args) {
PayProxy payProxy = new PayProxy(new Alipay());
Pay pay = payProxy.getProxy();
pay.pay(); //支付寶支付 執(zhí)行了0毫秒
}
}
我們通過arthas工具進行反編譯,可以找到真正的代理類$Proxy0
//代理對象
public final class $Proxy0 extends Proxy implements Pay {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
// 通過反射獲取名叫pay的menthod
m3 = Class.forName("com.designpattern.structure.proxy.v2.Pay").getMethod("pay", new Class[0]);
return;
}
public final void pay() {
// h是invocationHandler對象
this.h.invoke(this, m3, null);
return;
}
}
總結(jié)執(zhí)行流程如下
- 測試代碼里執(zhí)行了pay.pay()
- 根據(jù)多態(tài)的特性,執(zhí)行的是代理類($Proxy0)中的pay方法
- 代理類($Proxy0)中的pay方法中執(zhí)行了invocationHandler對象的invoke方法
- invocationHandler對象的invoke方法就是業(yè)務代碼中傳入的匿名內(nèi)部類中重寫的invoke方法
- 在重寫的invoke方法中通過反射調(diào)用真實對象alipay的pay方法
2.CGLib動態(tài)代理
JDK動態(tài)代理要求必須定義接口,如果沒有定義接口,就可以使用CGLib動態(tài)代理,CGLib為JDK的動態(tài)代理提供了很好的補充
首先引入cglib-3.3.0.jar與asm-9.0.jar
業(yè)務代碼
//真實對象
class Alipay implements Pay {
@Override
public void pay() {
System.out.println("支付寶支付");
}
}
class AlipayProxy implements MethodInterceptor {
//組合真實對象
private Alipay alipay = new Alipay();
public Alipay getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Alipay.class);
//設置回調(diào)函數(shù)
enhancer.setCallback(this);
//返回代理對象
return (Alipay) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(alipay, args);
System.out.println("執(zhí)行了" + (System.currentTimeMillis() - startTime) + "毫秒");
return result;
}
}測試代碼
public class Client {
public static void main(String[] args) {
Alipay proxy = new AlipayProxy().getProxy();
proxy.pay(); //支付寶支付 執(zhí)行了0毫秒
}
}
七.JDK代理與CGLIB代理對比
- JDK代理要求必須定義接口,CGLib不用
- CGLib的原理是動態(tài)生成被代理類的子類,所以類和方法都不能定義成final
- CGLib代理速度>JDK代理速度的場景:JDK1.6之前、JDK1.6與JDK1.7進行大量調(diào)用,其余場景JDK代理速度更快(因此在有接口的情況下推薦使用JDK動態(tài)代理)
八.代理模式的優(yōu)缺點
優(yōu)點
- 保護真實對象,使用代理對象與客戶端交互
- 符合開閉原則
- 客戶端與真實對象之間解耦
缺點
- 代理類的創(chuàng)建,增加了系統(tǒng)復雜度
九.使用場景
1.功能擴展:日志、監(jiān)控、事務
2.控制管理:權(quán)限、限流
3.遠程代理:FeignClient、RMI
4.動態(tài)邏輯:mybatis mapper、jpa
5.延遲加載:虛代理
十.通用的動態(tài)代理實現(xiàn)(拓展)
上文提到靜態(tài)代理是一個具體類產(chǎn)生一個代理類,可能會造成類爆炸,我們現(xiàn)在反觀動態(tài)代理則是一個接口產(chǎn)生一個代理類,也可能會造成類爆炸,所以這里給出一個較為通用的實現(xiàn)
業(yè)務代碼
//記錄執(zhí)行的時間的通用類
public class TimeRecordProxy<T> {
private final T target;
public TimeRecordProxy(T target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public T getProxy() {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this::invoke);
}
private Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("執(zhí)行了" + (System.currentTimeMillis()-startTime) + "毫秒");
return result;
}
}
測試代碼
public class Client {
public static void main(String[] args) {
TimeRecordProxy<Pay> timeRecordProxy = new TimeRecordProxy<>(new Alipay());
timeRecordProxy.getProxy().pay(); //支付寶支付 執(zhí)行了0毫秒
}
}
Spring AOP是代理模式的典型應用
到此這篇關(guān)于Java結(jié)構(gòu)型模式之代理模式詳解的文章就介紹到這了,更多相關(guān)Java代理模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決idea?中?SpringBoot?點擊運行沒反應按鈕成灰色的問題
在使用 Spring Boot 開發(fā)項目時,可能會遇到一個問題:點擊運行按鈕后,控制臺沒有任何輸出,項目界面也沒有顯示,這種情況可能是由多種原因?qū)е碌?,本文將介紹一些常見的解決方法,需要的朋友可以參考下2023-08-08
SpringBoot集成PDFBox實現(xiàn)電子簽章的代碼詳解
Apache PDFBox 是一個開源的 Java 庫,用于處理 PDF 文檔,它提供了一系列強大的功能,包括創(chuàng)建、渲染、拆分、合并、加密、解密 PDF 文件,以及從 PDF 中提取文本和元數(shù)據(jù)等,本文給大家介紹了SpringBoot集成PDFBox實現(xiàn)電子簽章,需要的朋友可以參考下2024-09-09
spring單元測試下模擬rabbitmq的實現(xiàn)
這篇文章主要介紹了spring單元測試下模擬rabbitmq的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-05-05
Java中使用While語句自增運算遍歷數(shù)組典型實例
這篇文章主要介紹了Java中使用While語句自增運算遍歷數(shù)組典型實例,本文直接給出實例代碼,并對每一句代碼都注解了詳細注釋,需要的朋友可以參考下2015-06-06
springboot Jpa多數(shù)據(jù)源(不同庫)配置過程
這篇文章主要介紹了springboot Jpa多數(shù)據(jù)源(不同庫)配置過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

