Java結(jié)構(gòu)型模式之代理模式詳解
一.介紹
在代理模式(Proxy Pattern)屬于結(jié)構(gòu)型模式。在代理模式中,我們對(duì)一個(gè)對(duì)象提供一個(gè)代理對(duì)象,使用代理對(duì)象控制原對(duì)象的引用,目的是為了透明的控制對(duì)象訪問(wèn)
二.UML類圖
三.代理模式分類
Java中的代理按照代理類生成時(shí)機(jī)不同分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理的代理類在編譯器就生成,而動(dòng)態(tài)代理的代理類在Java運(yùn)行時(shí)動(dòng)態(tài)生成。動(dòng)態(tài)代理又分為JDK代理和CGLib代理。
四.靜態(tài)代理
業(yè)務(wù)代碼
/** * 靜態(tài)代理 */ public interface Pay { void pay(); } //真實(shí)類 class Alipay implements Pay { @Override public void pay() { System.out.println("支付寶支付"); } } //代理類 class AlipayProxy implements Pay{ //組合真實(shí)對(duì)象 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毫秒 } }
測(cè)試代碼
public class Client { public static void main(String[] args) { new AlipayProxy().pay(); } }
五.靜態(tài)代理的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 符合開閉原則
- 功能增強(qiáng)無(wú)需改動(dòng)原業(yè)務(wù)代碼(解耦)
缺點(diǎn)
- 一個(gè)具體類就要產(chǎn)生一個(gè)代理類,可能會(huì)造成類爆炸
六.動(dòng)態(tài)代理
為了彌補(bǔ)靜態(tài)代理的缺點(diǎn),引入了動(dòng)態(tài)代理
1.JDK動(dòng)態(tài)代理(利用Java提供的代理機(jī)制)
業(yè)務(wù)代碼
/** * JDK動(dòng)態(tài)代理 */ public interface Pay { void pay(); } //真實(shí)類 class Alipay implements Pay { @Override public void pay() { System.out.println("支付寶支付"); } } class PayProxy { //組合真實(shí)對(duì)象 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; } }); } }
測(cè)試代碼
public class Client { public static void main(String[] args) { PayProxy payProxy = new PayProxy(new Alipay()); Pay pay = payProxy.getProxy(); pay.pay(); //支付寶支付 執(zhí)行了0毫秒 } }
我們通過(guò)arthas工具進(jìn)行反編譯,可以找到真正的代理類$Proxy0
//代理對(duì)象 public final class $Proxy0 extends Proxy implements Pay { private static Method m3; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { // 通過(guò)反射獲取名叫pay的menthod m3 = Class.forName("com.designpattern.structure.proxy.v2.Pay").getMethod("pay", new Class[0]); return; } public final void pay() { // h是invocationHandler對(duì)象 this.h.invoke(this, m3, null); return; } }
總結(jié)執(zhí)行流程如下
- 測(cè)試代碼里執(zhí)行了pay.pay()
- 根據(jù)多態(tài)的特性,執(zhí)行的是代理類($Proxy0)中的pay方法
- 代理類($Proxy0)中的pay方法中執(zhí)行了invocationHandler對(duì)象的invoke方法
- invocationHandler對(duì)象的invoke方法就是業(yè)務(wù)代碼中傳入的匿名內(nèi)部類中重寫的invoke方法
- 在重寫的invoke方法中通過(guò)反射調(diào)用真實(shí)對(duì)象alipay的pay方法
2.CGLib動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理要求必須定義接口,如果沒(méi)有定義接口,就可以使用CGLib動(dòng)態(tài)代理,CGLib為JDK的動(dòng)態(tài)代理提供了很好的補(bǔ)充
首先引入cglib-3.3.0.jar與asm-9.0.jar
業(yè)務(wù)代碼
//真實(shí)對(duì)象 class Alipay implements Pay { @Override public void pay() { System.out.println("支付寶支付"); } } class AlipayProxy implements MethodInterceptor { //組合真實(shí)對(duì)象 private Alipay alipay = new Alipay(); public Alipay getProxy(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Alipay.class); //設(shè)置回調(diào)函數(shù) enhancer.setCallback(this); //返回代理對(duì)象 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; } }
測(cè)試代碼
public class Client { public static void main(String[] args) { Alipay proxy = new AlipayProxy().getProxy(); proxy.pay(); //支付寶支付 執(zhí)行了0毫秒 } }
七.JDK代理與CGLIB代理對(duì)比
- JDK代理要求必須定義接口,CGLib不用
- CGLib的原理是動(dòng)態(tài)生成被代理類的子類,所以類和方法都不能定義成final
- CGLib代理速度>JDK代理速度的場(chǎng)景:JDK1.6之前、JDK1.6與JDK1.7進(jìn)行大量調(diào)用,其余場(chǎng)景JDK代理速度更快(因此在有接口的情況下推薦使用JDK動(dòng)態(tài)代理)
八.代理模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 保護(hù)真實(shí)對(duì)象,使用代理對(duì)象與客戶端交互
- 符合開閉原則
- 客戶端與真實(shí)對(duì)象之間解耦
缺點(diǎn)
- 代理類的創(chuàng)建,增加了系統(tǒng)復(fù)雜度
九.使用場(chǎng)景
1.功能擴(kuò)展:日志、監(jiān)控、事務(wù)
2.控制管理:權(quán)限、限流
3.遠(yuǎn)程代理:FeignClient、RMI
4.動(dòng)態(tài)邏輯:mybatis mapper、jpa
5.延遲加載:虛代理
十.通用的動(dòng)態(tài)代理實(shí)現(xiàn)(拓展)
上文提到靜態(tài)代理是一個(gè)具體類產(chǎn)生一個(gè)代理類,可能會(huì)造成類爆炸,我們現(xiàn)在反觀動(dòng)態(tài)代理則是一個(gè)接口產(chǎn)生一個(gè)代理類,也可能會(huì)造成類爆炸,所以這里給出一個(gè)較為通用的實(shí)現(xiàn)
業(yè)務(wù)代碼
//記錄執(zhí)行的時(shí)間的通用類 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; } }
測(cè)試代碼
public class Client { public static void main(String[] args) { TimeRecordProxy<Pay> timeRecordProxy = new TimeRecordProxy<>(new Alipay()); timeRecordProxy.getProxy().pay(); //支付寶支付 執(zhí)行了0毫秒 } }
Spring AOP是代理模式的典型應(yīng)用
到此這篇關(guān)于Java結(jié)構(gòu)型模式之代理模式詳解的文章就介紹到這了,更多相關(guān)Java代理模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java replaceAll()方法報(bào)錯(cuò)Illegal group reference的解決辦法
這篇文章主要給大家介紹了關(guān)于Java replaceAll()方法報(bào)錯(cuò)Illegal group reference的解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09使用idea生成springboot程序的docker鏡像的操作指南
這篇文章給大家詳細(xì)的介紹了使用idea生成springboot程序的docker鏡像的操作指南,文中通過(guò)圖文結(jié)合給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12springboot中JSONObject遍歷并替換部分json值
這篇文章主要介紹了springboot中JSONObject遍歷并替換部分json值,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11springboot整合nacos,如何讀取nacos配置文件
這篇文章主要介紹了springboot整合nacos,如何讀取nacos配置文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11解決Springboot項(xiàng)目報(bào)錯(cuò):java:錯(cuò)誤:不支持發(fā)行版本?17
這篇文章主要給大家介紹了關(guān)于解決Springboot項(xiàng)目報(bào)錯(cuò):java:錯(cuò)誤:不支持發(fā)行版本17的相關(guān)資料,這個(gè)錯(cuò)誤意味著你的Spring Boot項(xiàng)目正在使用Java 17這個(gè)版本,但是你的項(xiàng)目中未配置正確的Java版本,需要的朋友可以參考下2023-08-08Springboot應(yīng)用gradle?Plugin示例詳解
這篇文章主要介紹了Springboot應(yīng)用gradle?Plugin詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04詳解Java語(yǔ)言中一個(gè)字符占幾個(gè)字節(jié)?
這篇文章主要介紹了Java語(yǔ)言中一個(gè)字符占幾個(gè)字節(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04java 根據(jù)坐標(biāo)截取圖片實(shí)例代碼
這篇文章主要介紹了java 根據(jù)坐標(biāo)截取圖片實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03