設計模式——結構型模式——代理模式(靜態代理、動態代理:JDK、CGLIB)

目錄

代理模式

代理模式簡介

代理模式的分類

代理模式組成

代理模式的優缺點

靜態代理

背景前置

編寫代碼

JDK動態代理

編寫代碼

使用Arthas分析JDK動態代理底層原理

CGLIB動態代理

編寫代碼

三種代理的對比

?代理模式使用場景

代理模式

代理模式簡介

????????代理模式屬于結構型模式。指一個對象本身不做實際的操作,而是通過其他對象來獲取自己想要的結果。? ? ? ?

????????產生背景:由于某些原因需要給某對象提供一個代理以控制對該對象的訪問。這時,訪問對象不適合或者不能直接引用目標對象,代理對象作為訪問對象和目標對象之間的中介。

? ? ? ? 意義:目標對象只需要關注自己的實現細節,通過代理來實現功能的增強,可以擴展目標對象的功能。同時體現了非常重要的變成模式,不能隨便修改目標對象的源碼,如果需要修改目標對象的源碼對已有功能進行增強,此時可以通過修改代理的方式實現功能的擴展。

? ? ? ? 例子:如果某人需要租房,此時可以借助于房屋中介或租賃公司,此時房屋中介或租賃公司相當于代理,可以讓租房人找到適合自己的房屋。

代理模式的分類

? ? ? ? 靜態代理:靜態代理就是在程序運行之前,代理類字節碼.class就已編譯好,通常一個靜態代理類也只代理一個目標類,代理類和目標類都實現相同的接口。

? ? ? ? 動態代理:動態代理類與靜態代理類最主要不同的是,代理類的字節碼不是在程序運行前生成的,而是在程序運行時在虛擬機中程序自動創建的。

代理模式組成

? ? ? ? 代理(Proxy)模式可以分為三種角色:

  • 抽象主題(Subject)類: 通過接口或抽象類聲明真實主題和代理對象實現的業務方法。

  • 真實主題(Real Subject)類: 實現了抽象主題中的具體業務,是代理對象所代表的真實對象,是最終要引用的對象。

  • 代理(Proxy)類 : 提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。

代理模式的優缺點

? ? ? ? 優點:保護、擴展、降低耦合度。

  • 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;

  • 代理對象可以擴展目標對象的功能;

  • 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度;

? ? ? ? 缺點:增加系統的復雜性。

靜態代理

背景前置

????????火車站賣票:如果要買火車票的話,需要去火車站買票,坐車到火車站,排隊等一系列的操作,顯然比較麻煩。而火車站在多個地方都有代售點,我們去代售點買票就方便很多了。這個例子其實就是典型的代理模式,火車站是目標對象,代售點是代理對象。類圖如下:

編寫代碼

? ? ? ? 如上面背景所示,我們需要定義①抽象主題類:買票接口類。②真實主題類:代理的目標類(火車站)。③代理類:代售點,以實現對真實主題類功能的增強。

/*** 聲明抽象類、定義公共方法*/
public interface SallTicket {void sale();
}/**
* 火車站類,實現買票接口
*/
public class TrainStation implements SallTicket{@Overridepublic void sale() {System.out.println("火車站正在賣票...");}
}/**
* 代理類:與目標類實現相同的接口,以達到對目標類方法的增強
*/public class SallProxy implements SallTicket{private TrainStation trainStation;public SallProxy(TrainStation trainStation){this.trainStation = trainStation;}@Overridepublic void sale() {System.out.println("代售點收取少量服務費...");trainStation.sale();}
}/*** 編寫客戶端測試類*/
public class Client {public static void main(String[] args) {SallProxy sallProxy = new SallProxy(new TrainStation());sallProxy.sale();}
}

接口、目標類、代理類之間的關系圖:

  • 接口類SallTicket:定義了賣票的抽象公共方法sale()。
  • 目標類TrainStation:實現了接口類SallTicket,并重寫方法sale()。
  • 代理類SallProxy:實現接口類SallTicket,且在方法內部傳遞了接口類的實現類也即目標類TrainStation。在代理類內部實現了原有賣票方法sale()的功能的增強,并且調用目標類TrainStation中的sale()方法達到了賣票的功能。
  • 測試類Client:構建代理類,并實現功能的驗證。

JDK動態代理

編寫代碼

????????JDK動態代理和靜態代理不同的地方在于代理類的編寫不同,因此接口類、目標類仍然沿用靜態代理中創建的,編寫JDK動態代理程序結構如下:

????????其中代理類(ProxyFactory)和測試類(Client)代碼如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*** 獲取代理對象的工廠類* 代理類也實現了對應的接口*/
public class ProxyFactory {// 聲明目標對象private TrainStation station = new TrainStation();// 獲取代理對象的方法public SallTicket getProxyObject(){// 返回代理對象、代理對象也實現了目標接口/*** ClassLoader loader, 類加載器,用于加載代理類,可以通過目標對象獲取類加載器* Class<?>[] interfaces, 代理類實現的接口的字節碼對象* InvocationHandler h, 代理對象的調用處理程序*/SallTicket proxyObject = (SallTicket) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(), // 目標對象和代理類實現了相同的接口,可以通過目標對象獲取代理類實現的接口的字節碼對象new InvocationHandler() {   // 匿名內部類/*** @param proxy 代理對象,就是proxyObject* @param method 對接口中的方法進行封裝的method對象* @param args 調用方法的實際參數* @return  返回值就是方法的返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 對目標對象進行方法的增強System.out.println("代售點收取一定的服務費用(JDK)...");// 執行目標對象的方法Object obj = method.invoke(station, args);return obj;}});return proxyObject;}
}/*** 客戶端,測試JDK動態代理對方法的增強*/
public class Client {public static void main(String[] args) {// 創建代理對象工廠ProxyFactory proxyFactory = new ProxyFactory();// 使用proxyFactory對象的方法獲取代理對象SallTicket proxyObject = proxyFactory.getProxyObject();// 調用目標對象的方法proxyObject.sale();}
}

???????接口、目標類、代理類之間的關系圖:?

? ? ? ? 上圖中的ProxyFactory類并不是真正的JDK動態代理類,其本質上是一個代理工廠,通過Proxy類的靜態方法newProxyInstance(...),傳入目標類的字節碼,代理類實現的接口,invocationHandler接口的實現內部類等參數在程序運行的時候動態的創建代理類,并且借助反射獲取代理對象的方法,并且實現對目標方法的增強。

使用Arthas分析JDK動態代理底層原理

? ? ? ? 在測試類Client中添加以下代碼用于獲取代理類的Class對象。

? ? ? ? 隨后借助于阿里開源工具Arthas,使用反編譯命令jad?com.sun.proxy.$Proxy0獲取已加載到 JVM 中的類的源代碼如下:

package com.sun.proxy;import com.itheima.proxy.dynamic.jdk.SellTickets;
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 SellTickets {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void sell() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

從上面的類中,我們可以看到以下幾個信息:

  • 代理類($Proxy0)實現了SellTickets。

  • 代理類($Proxy0)將我們提供了的匿名內部類對象傳遞給了父類。

? ? ? ? 可以分析代理類執行流程如下:

  1. 在測試類中通過代理對象調用sell()方法。
  2. 根據多態的特性,執行的是代理類($Proxy0)中的sall()方法。
  3. 代理類($Proxy0)中的sell()方法中又調用了InvocationHandler接口的子實現類對象的invoke方法。
  4. invoke方法通過反射執行了真實對象所屬類(TrainStation)中的sall()方法。

CGLIB動態代理

編寫代碼

????????CGLIB動態代理和靜態代理不同的地方在于代理類的編寫不同,并且不需要接口類,因此目標類仍然沿用靜態代理中創建的,編寫CGLIB動態代理程序結構如下:

? ? ? ? CGLIB是第三方提供的包,編寫代碼之前需要先引入jar包。

<dependency>
? ? <groupId>cglib</groupId>
? ? <artifactId>cglib</artifactId>
? ? <version>2.2.2</version>
</dependency>

/*** 代理工廠*/
public class ProxyFactory implements MethodInterceptor {private TrainStation station = new TrainStation();// 獲取代理對象public TrainStation getProxyObject(){// 創建Enhancer對象,類似于JDK動態代理的Proxy類Enhancer enhancer = new Enhancer();// 設置父類的字節碼對象enhancer.setSuperclass(station.getClass());// 設置回調函數,傳遞的對象,是MethodInterceptor的子實現類對象enhancer.setCallback(this);// 創建代理對象TrainStation proxyObject = (TrainStation) enhancer.create();return proxyObject;}/*** @param o      代理對象* @param method 真實對象中的方法的Method實例* @param objects  實際參數* @param methodProxy 代理對象中的方法的method實例* @return 代理對象方法的返回值* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("代理點收取一些服務費用(CGLIB動態代理方式)");TrainStation result = (TrainStation) method.invoke(station, objects);return result;}
}/*** 客戶端*/
public class Client {public static void main(String[] args) {// 創建代理對象工廠ProxyFactory proxyFactory = new ProxyFactory();// 獲取代理對象TrainStation proxyObject = proxyFactory.getProxyObject();proxyObject.sale();}
}

?????????接口、目標類、代理類之間的關系圖:

????????簡單原理:通過自定義實現攔截器接口(MethodInterceptor)的類【也就是目標類】,并重寫intercept()用于攔截增強被代理類的方法【類似于JDK動態代理中的invoke()方法】。通過Enhancer 類的 create()創建簡單的代理類。

????????CGLIB采用非常底層的字節碼技術,通過字節碼技術為一個類創建子類,并在子類中采用方法攔截的技術的攔截所有父類的方法調用,順勢織入橫切邏輯。(CGLIB在字節碼的基礎上,利用ASM開源包,將代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理)

三種代理的對比

  • 動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。如果接口增加一個方法,靜態代理模式除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。而動態代理不會出現該問題。

  • JDK動態代理只能代理實現了接口的類,而CGLIB可以代理未實現任何接口的類。
  • JDK動態代理是實現了被代理對象所實現的接口CGLIB是繼承了被代理對象。
  • JDK和CGLIB都是在運行期生成字節碼,JDK是直接寫Class字節碼,CGLIB是使用ASM框架寫Class字節碼。
  • CGLib不能對聲明為final的類或者方法進行代理,因為CGLib原理是動態生成被代理類的子類。
  • 在JDK1.6、JDK1.7、JDK1.8逐步對JDK動態代理優化之后,在調用次數較少的情況下,JDK代理效率高于CGLib代理效率,只有當進行大量調用的時候,JDK1.6和JDK1.7比CGLib代理效率低一點,但是到JDK1.8的時候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK動態代理,如果沒有接口使用CGLIB代理。

?代理模式使用場景

? ? ? ? 此部分內容暫時不是很熟悉,等以后學習到再慢慢補充。

  • 遠程(Remote)代理

    本地服務通過網絡請求遠程服務。為了實現本地到遠程的通信,我們需要實現網絡通信,處理其中可能的異常。為良好的代碼設計和可維護性,我們將網絡通信部分隱藏起來,只暴露給本地服務一個接口,通過該接口即可訪問遠程服務提供的功能,而不必過多關心通信部分的細節。

  • 防火墻(Firewall)代理

    當你將瀏覽器配置成使用代理功能時,防火墻就將你的瀏覽器的請求轉給互聯網;當互聯網返回響應時,代理服務器再把它轉給你的瀏覽器。

  • 保護(Protect or Access)代理

    控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用權限。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10850.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10850.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10850.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mybatis操作數據庫的兩種方式:Mapper代理模式

1.Mapper代理模式的特點 程序員沒有寫接口的子實現——直接獲取數據庫的數據 因為Mybatis定義了一套規則&#xff0c;對方法進行了實現&#xff0c;程序員只要遵循這套方法就可以直接使用 2.如何實現Mapper代理模式 步驟&#xff1a; 1.創建一個dao接口&#xff0c;在接口…

java項目之英語知識應用網站源碼(springboot+vue+mysql)

風定落花生&#xff0c;歌聲逐流水&#xff0c;大家好我是風歌&#xff0c;混跡在java圈的辛苦碼農。今天要和大家聊的是一款基于springboot的英語知識應用網站。項目源碼以及部署相關請聯系風歌&#xff0c;文末附上聯系信息 。 項目簡介&#xff1a; 英語知識應用網站的主要…

【免費】AME最新Adobe Media Encoder電腦軟件安裝包2024-2018支持WIN和MAC

Adobe MediaEncoder「Me」2024是一款功能強大的轉碼和媒體處理軟件&#xff0c;它不僅能輕松應對各種媒體文件的編碼和導出需求&#xff0c;還支持多種視頻格式和分辨率&#xff0c;讓你的視頻處理變得更加高效。此外&#xff0c;該軟件界面簡潔明了&#xff0c;操作簡便&#…

【一步一步了解Java系列】:了解Java與C語言的運算符的“大同小異”

看到這句話的時候證明&#xff1a;此刻你我都在努力~ 加油陌生人~ 個人主頁&#xff1a; Gu Gu Study ?? 專欄&#xff1a;一步一步了解Java 喜歡的一句話&#xff1a; 常常會回顧努力的自己&#xff0c;所以要為自己的努…

【Element-UI快速入門】

文章目錄 **Element-UI快速入門****一、Element-UI簡介****二、安裝Element-UI****三、引入Element-UI****四、使用Element-UI組件****五、自定義Element-UI組件樣式****六、Element-UI布局組件****七、Element-UI表單組件****八、插槽&#xff08;Slots&#xff09;和主題定制…

【數據結構】排序(一)—— 希爾排序(思路演進版)

目錄 一、常見的排序算法分類 二、常見排序算法的實現 2.1插入排序 2.1.1基本思想 2.1.2直接插入排序 思路 step1.單趟控制 step2.總體控制 代碼實現 測試 特性總結 2.1.3 希爾排序( 縮小增量排序 ) 基本思想 思路演進 &#x1f308;1.代碼實現單組排序&#…

你能堅持二十年如一日地積極試錯嗎?

你能堅持二十年如一日地積極試錯嗎&#xff1f;先說一個大家都耳熟能詳的人物&#xff1a;克里斯托弗哥倫布&#xff0c;他被稱為新大陸的發現者&#xff0c;是具有極高歷史地位的偉大航海家。 但是新大陸本來就不是所謂的“新”大陸&#xff0c;而是在4萬年前從白令海峽遷徙過…

端午節線上活動方案怎么寫?

一年一端午&#xff0c;一歲一安康。 如果您想組織端午活動&#xff0c;卻不知道如何安排&#xff0c;可以看看何策網&#xff0c;有很多案例參考&#xff0c;仿造模板修改即可。 下面分享一個線上端午節活動策劃方案&#xff0c;希望能幫到你&#xff01; 端午節作為祭祖祈…

Qt 實現TCP 協議的斷開重連

在Qt中實現TCP斷開重連&#xff0c;你可以使用QTcpSocket類&#xff0c;并結合QTimer來處理重連邏輯&#xff0c;在Qt中實現TCP斷開后的自動重連功能&#xff0c;通常可以通過以下步驟進行&#xff1a; 1. 初始化QTcpSocket&#xff1a; 首先&#xff0c;需要創建一個QTcpSock…

Docker使用注意事項

docker import 和 docker load 有什么區別&#xff1f; 想要了解 docker load 與 docker import 命令的區別&#xff0c;還必須知道 docker save 與 docker export docker save&#xff1a;將一個鏡像導出為文件&#xff0c;再使用docker load命令將文件導入為一個鏡像&#…

mysql集群NDBcluster引擎在寫入數據時報錯 (1114, “The table ‘ads‘ is full“)

問題描述&#xff1a;mysql集群在寫入數據時&#xff0c;出現上述報錯 問題原因&#xff1a;表數據已滿&#xff0c;一般是在集群的管理節點設置里面datamemory的值太小&#xff0c;當數據量超過該值時就會出現該問題 解決方案&#xff1a; 修改集群管理節點的config.ini里面…

ICode國際青少年編程競賽- Python-4級訓練場-嵌套for循環練習2

ICode國際青少年編程競賽- Python-4級訓練場-嵌套for循環練習2 1、 for i in range(3):Dev.turnRight()for j in range(3):Dev.step(-3)Dev.turnRight()Dev.step(4-2*i)2、 for i in range(6):for j in range(2):Dev.step(2 2 * i)if i > 3: Dev.step(i - 2)Dev.turnRi…

更相減損術求最大公約數

1.定義 更相減損術是出自《九章算術》的一種求最大公約數的算法&#xff0c;它原本是為約分而設計的&#xff0c;但它適用于任何需要求最大公約數的場合。 原文是&#xff1a; 可半者半之&#xff0c;不可半者&#xff0c;副置分母、子之數&#xff0c;以少減多&#xff0c;…

C++小程序:同一路由器下兩臺計算機間簡單通信(2/2)——客戶端

客戶端的程序結構前半部分與服務器端基本相同&#xff0c;后半部分也相對簡單。相關函數的解釋可以參考前文服務器端的內容。有關客戶端的內容除個別地方外&#xff0c;就不再做長篇大論的解釋。強調一點&#xff0c;如果將此程序移到其它電腦上運行&#xff0c;編譯需要releas…

Ciphey無法安裝的解決辦法

安裝過程純屬自己實踐&#xff0c;滿滿干貨 困擾我幾天的問題終于解決了 我看著教程在window上安裝 python3.8/python3.9/python3.10無論如何都安裝不上&#xff0c; 在win10虛擬機仍然安裝不上 可能是我電腦環境問題 解決辦法&#xff1a; 在kali中安裝&#xff0c;但是…

18_文件系統的制作-Ramdisk

文件系統的制作(Ramdisk) 本文介紹如何制作文件系統。另外, 由于Busybox 集合了很多工具,編譯起來也非常方便。在講解制作文件系統的時候,也會介紹 busybox 的編譯和安裝過程;介紹制作文件系統時,會詳細介紹 Ramdisk 、 YAFFS2、JFFS2 及其它文件系統的制作。 1. 根文件系…

列表、字典、集合推導式

文章目錄 前言1.列表推導式&#xff08;List Comprehension&#xff09;:2 字典推導式&#xff08;Dictionary Comprehension&#xff09;3 集合推導式&#xff08;Set Comprehension) 前言 在Python中&#xff0c;列表、字典、集合推導式是一種便捷的語法&#xff0c;用于根據…

第13節 第二種shellcode編寫實戰(2)

在第二種shellcode編寫實戰(1)的基礎上&#xff0c;新增加一個CAPI類&#xff0c;將所有用到的函數都在這個類中做動態調用的處理&#xff0c;這樣使得整個shellcode功能結構更加清晰。 1. 新建類CAPI&#xff08;即api.h和api.cpp兩個文件&#xff09;&#xff1a; api.h&…

#DELPHI BASS庫Windows平臺下,實時更換輸出設備

DELPHI BASS庫Windows平臺下&#xff0c;實時更換輸出設備 #DELPHI BASS庫Windows平臺下&#xff0c;實時更換輸出設備 取自網絡&#xff0c;分享&#xff0c;項目嵌入無損音樂播放后&#xff0c;畫蛇添足的功能分享&#xff01; 直接貼核心代碼&#xff0c;看不明白去看說…

flutter自定義日期選擇器按日、按月、自定義開始、結束時間

導入包flutter_datetime_picker: 1.5.0 封裝 import package:atui/jade/utils/JadeColors.dart; import package:flutter/cupertino.dart; import package:flutter/material.dart; import package:flutter_datetime_picker/flutter_datetime_picker.dart; import package:flut…