第06天 靜態代理和動態代理

每天學習一個知識點

?作者簡介:大家好,我是Leo,熱愛Java后端開發者,一個想要與大家共同進步的男人😉😉
🍎個人主頁:Leo的博客
💞當前專欄:每天一個知識點
?特色專欄: MySQL學習
🥭本文內容:第06天 靜態代理和動態代理
🖥?個人小站 :個人博客,歡迎大家訪問
📚個人知識庫: 知識庫,歡迎大家訪問

在了解靜態代理和動態代理之前,我們需要先了解一下什么是代理模式

1. 代理模式

代理模式是一種比較好理解的設計模式。簡單來說就是 我們使用代理對象來代替對真實對象(real object)的訪問,這樣就可以在不修改原目標對象的前提下,提供額外的功能操作,擴展目標對象的功能。

代理模式的主要作用是擴展目標對象的功能,比如說在目標對象的某個方法執行前后你可以增加一些自定義的操作。

  1. 代理模式: 為一個對象提供一個替身,以控制對這個對象的訪問。即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能
  2. 被代理的對象可以是遠程對象、創建開銷大的對象或需要安全控制的對象

舉個例子:當我們工作之后需要出去租房子,房東張貼廣告帶我看房子,最后簽合同,但是房東只想坐著簽合同并不想到處跑著看房子,于是就找了一個中介專門來宣傳廣告并且帶租戶看房子,而房東只負責簽合同收錢!中介在這里就可以看作是代理你的代理對象代理的行為(方法)是帶租戶看房子。

image-20230814112036352

代理模式有靜態代理和動態代理兩種實現方式,我們 先來看一下靜態代理模式的實現。

2.靜態代理

靜態代理中,我們對目標對象的每個方法的增強都是手動完成的,非常不靈活(比如接口一旦新增加方法,目標對象和代理對象都要進行修改)且麻煩(需要對每個目標類都單獨寫一個代理類)。 實際應用場景非常非常少,日常開發幾乎看不到使用靜態代理的場景。

上面我們是從實現和應用角度來說的靜態代理,從 JVM 層面來說, 靜態代理在編譯時就將接口、實現類、代理類這些都變成了一個個實際的 class 文件。

靜態代理實現步驟:

  1. 定義一個接口及其實現類;
  2. 創建一個代理類同樣實現這個接口
  3. 將目標對象注入進代理類,然后在代理類的對應方法調用目標類中的對應方法。這樣的話,我們就可以通過代理類屏蔽對目標對象的訪問,并且可以在目標方法執行前后做一些自己想做的事情。

下面通過代碼來進一步了解靜態代理的應用

  1. 定義一個賣房的接口

    public interface UserService {String sell(String message);
    }
    
  2. 實現賣房接口

    public class UserServiceImpl implements UserService{public String sell(String message) {System.out.println("我是房東-->我要賣房,找我簽合同");return message;}
    }
    
  3. 創建代理類實現賣房接口

    public class UserServiceProxy implements UserService{private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic String sell(String message) {System.out.println("我是中介,我可以帶你去看房");userService.sell(message);//調用方法之后,我們同樣可以添加自己的操作System.out.println("after method send()");return null;}
    }
    
  4. 測試

    @org.junit.Testpublic void name() {UserServiceImpl userService = new UserServiceImpl();UserServiceProxy userServiceProxy = new UserServiceProxy(userService);userServiceProxy.sell("賣房");}
    }
    

    控制臺打印 :

    image-20230814114502067

靜態代理存在的問題

1. 靜態類文件數量過多,不利于項目管理UserServiceImpl  UserServiceProxyOrderServiceImpl OrderServiceProxy
2. 額外功能維護性差代理類中 額外功能修改復雜(麻煩)

3. 動態代理

相比于靜態代理來說,動態代理更加靈活。我們不需要針對每個目標類都單獨創建一個代理類,并且也不需要我們必須實現接口,我們可以直接代理實現類( CGLIB 動態代理機制)。

從 JVM 角度來說,動態代理是在運行時動態生成類字節碼,并加載到 JVM 中的。

說到動態代理,Spring AOP、RPC 框架應該是兩個不得不提的,它們的實現都依賴了動態代理。

動態代理在我們日常開發中使用的相對較少,但是在框架中的幾乎是必用的一門技術。學會了動態代理之后,對于我們理解和學習各種框架的原理也非常有幫助。

就 Java 來說,動態代理的實現方式有很多種,比如 JDK 動態代理CGLIB 動態代理等等。

概念:通過代理類為原始類(目標類)增加額外功能,代理類由Spring動態生成。
好處:利于原始類(目標類)的維護

代理運行對象在程序運行的過程中動態的在內存進行構建,可以靈活的進行業務功能的切換。

3.1 JDK動態代理

JDK動態代理是基于 Java 的反射機制實現的。使用 JDK中接口和類實現代理對象的動態創建。JDK的動態代理要求目標對象必須實現接口,而代理對象不必實現業務接口,這是 java 設計上的要求。從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類和接口支持代理模式,它們分別Proxy, Method和 InvocationHandler。

  • 目標對象必須實現業務接口

  • JDK代理代理對象不需要實現業務接口

  • 動態代理的對象在程序運行中不存在

  • 動態代理靈活的進行業務功能的切換

1. Proxy類

? 通過JDK的 java.lang.reflect.Proxy 類實現動態代理,會使用其靜態方法 newProxyInstance(),依據目標對象、業務接口及調用處理器三者,自動生成一個動態代理對象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)loader:目標類的類加載器,通過目標對象的反射可獲取
interfaces:目標類實現的接口數組,通過目標對象的反射可獲取
handler:調用處理器。

2. Method類

invoke()方法的第二個參數為 Method 類對象,該類有一個方法也叫 invoke(),可以調用目標方法。這兩個 invoke()方法,雖然同名,但無關。

public Object invoke ( Object obj, Object... args)obj:表示目標對象
args:表示目標方法參數,就是其上一層 invoke 方法的第三個參數
該方法的作用是:調用執行 obj 對象所屬類的方法,這個方法由其調用者 Method 對象確定。在代碼中,一般的寫法為
method.invoke(target, args);
其中,method 為上一層 invoke 方法的第二個參數。這樣,即可調用了目標類的目標方法。

3. IocationHandler接口

InvocationHandler 接口叫做調用處理器,負責完成調用目標方法,并增強功能。通過代理對象執行目標接口中的方法 , 會把方法的調用分派給調用處理器(InvocationHandler)的實現類,執行實現類中的 invoke()方法,我們需要把功能代理寫在 invoke()方法中 。此接口中只有一個方法。

img

invoke 方法中可以截取對目標方法的調用。在這里進行功能增強。Java 的動態代理是建立在反射機制之上的。實現了 InvocationHandler 接口的類用于加強目標類的主業務邏輯。這個接口中有一個方法 invoke(),具體加強的代碼邏輯就是定義在該方法中的。通過代理對象執行接口中的方法時,會自動調用 invoke()方法。

4. 實現步驟

  1. 代理對象不需要實現接口。

  2. 代理對象的生成是利用JDK中的proxy類,動態的在內存中構建代理對象。

  3. 代碼實現接口

  4. ProxyFactory.java 代理實例生成工廠

    package com.leo.demo02.service.impl;import com.leo.demo02.service.UserService;import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;/*** @author : Leo* @version 1.0* @date 2023/8/14 13:40* @description :*/
    public class ProxyFactory {/**任何的代理對象,都要清楚目標對象,在此得設置一個目標對象*/private UserService userService;//傳入目標對象public ProxyFactory(UserService userService){this.userService = userService;}/** 給目標對象生成代理實例 */public Object getProxyInstance(){return Proxy.newProxyInstance(//指定當前目標對象,使用類加載器獲得userService.getClass().getClassLoader(),//獲得目標對象實現的所有接口userService.getClass().getInterfaces(),//處理代理實例上的方法并返回調用結果new InvocationHandler() {@Overridepublic Object invoke(//代理對象的實例Object proxy,//代理的目標對象的實現方法Method method,//代理的目標對象實現方法的參數Object[] args) throws Throwable {System.out.println("我是中介,我在張貼廣告......");System.out.println("我是中介,我在帶領租客看房子......");//目標對象執行自己的方法Object returnValue = method.invoke(userService, args);System.out.println("我是中介,我在帶領租客辦理結束流程");return returnValue;}});}
    }
    
    1. 測試類

       @org.junit.Testpublic void test03() {// 創建代理工廠對象ProxyFactory factory = new ProxyFactory(new UserServiceImpl());UserService service = (UserService) factory.getProxyInstance();service.sell("賣房");}
      

      控制臺打印 :

      image-20230814135707682

注意:JDK動態代理中,代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能用JDK動態代理。

3.2 CGlib動態代理

Code Generation Library ,又稱為子類通過動態的的在內存中構建子類對象,重寫父類方法進行代理功能的增強。

想要功能擴展,但目標對象沒有實現接口,怎樣功能擴展?

解決方案:子類的方式

Class subclass extends UserDao{}

以子類的方式實現(cglib代理),在內存中構建一個子類對象從而實現對目標對象功能的擴展。

1. CGlib動態代理的特點

  1. JDK動態代理有一個限制,就是使用動態代理的目標對象必須實現一個或多個接口。如果想代理沒有實現類的接口,就可以使用CGLIB進行代理。
  2. CGLIB是一個強大的高性能的代碼生成包,它可以在運行期擴展Java類與實現Java接口。它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception。
  3. CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM,來轉換字節碼并生成新的類。不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。

2. CGLIB的實現步驟

  1. 需要spring-core-5.2.5.jar依賴即可。

  2. 引入功能包后,就可以在內存中動態構建子類

  3. 被代理的類不能為final, 否則報錯。

  4. 目標對象的方法如果為final/static, 那么就不會被攔截,即不會執行目標對象額外的業務方法。

  5. 編寫 ProxyFactory.java

    	package com.leo.demo02.service.impl;import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author : Leo* @version 1.0* @date 2023/8/14 14:00* @description :*/
    public class ProxyFactory2 implements MethodInterceptor {/** 目標對象 */private Object target;/** 傳入目標對象 */public ProxyFactory2(Object target){this.target = target;}/** Cglib采用底層的字節碼技術,在子類中采用方法攔截的技術,攔截父類指定方法的調用,并順勢植入代理功能的代碼 */@Overridepublic Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {//代理對象的功能System.out.println("我是中介,我在張貼廣告......");System.out.println("我是中介,我在帶領租客看房子......");//調用目標對象的方法Object returnValue=method.invoke(target, arg2);//代理對象的功能System.out.println("我是中介,我在帶領租客辦理結束流程");return returnValue;}/** 生成代理對象 */public Object getProxyInstance(){//1.使用工具類Enhancer en=new Enhancer();//2.設置父類en.setSuperclass(target.getClass());//3.設置回調函數en.setCallback(this);//4.創建子類(代理)對象return en.create();}}
    
    1. 測試類

       @org.junit.Testpublic void test04() {UserService service = (UserService)new ProxyFactory2(new UserServiceImpl()).getProxyInstance();service.sell("賣房");
      }
      

      控制臺打印:

      image-20230814141003722

3.3 JDK 動態代理和 CGLIB 動態代理對比

JDK 動態代理只能代理實現了接口的類或者直接代理接口,而 CGLIB 可以代理未實現任何接口的類。 另外, CGLIB 動態代理是通過生成一個被代理類的子類來攔截被代理類的方法調用,因此不能代理聲明為 final 類型的類和方法。

就二者的效率來說,大部分情況都是 JDK 動態代理更優秀,隨著 JDK 版本的升級,這個優勢更加明顯。

4. 靜態代理和動態代理的對比

靈活性:動態代理更加靈活,不需要必須實現接口,可以直接代理實現類,并且可以不需要針對每個目標類都創建一個代理類。另外,靜態代理中,接口一旦新增加方法,目標對象和代理對象都要進行修改,這是非常麻煩的!

JVM 層面:靜態代理在編譯時就將接口、實現類、代理類這些都變成了一個個實際的 class 文件。而動態代理是在運行時動態生成類字節碼,并加載到 JVM 中的。

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

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

相關文章

36 | 銀行貸款數據分析

本文將以銀行貸款數據分析為主題,深入探討如何運用數據科學的方法,揭示銀行貸款領域的內在規律和趨勢。通過對貸款數據的分析,我們能夠洞察不同類型貸款的分布情況、貸款金額的變化趨勢,以及借款人的特征和還款情況等關鍵信息。 通過運用Python編程語言及相關的數據分析工…

arcgis定義投影與投影

1、定義 地理坐標系&#xff08;GCS&#xff09;&#xff1a;利用地球表面的經緯度表示的坐標系統。一般單位為度。投影坐標系&#xff08;PCS&#xff09;&#xff1a;利用數學換算將三維地球表面上的經緯度坐標轉換到二維平面上的坐標系統。一般單位為米。可以認為&#xff…

【ARM Cache 系列文章 9 番外篇 -- ARMv9 系列 Core 介紹】

文章目錄 ARMv9 系列CoreARM Cortex-A510 介紹ARM Cortex-A715ARM Cortex-A720 ARMv9 系列Core 2021年5月Arm公布了其最新3款CPU和3款GPU核心設計&#xff0c;三款新CPU分別是旗艦核心Cortex-X2、高性能核心Cortex-A710、高能效核心Cortex-A510 CPU&#xff0c;三款新GPU核心則…

【Unity每日一記】向量操作攝像機的移動(向量加減)

&#x1f468;?&#x1f4bb;個人主頁&#xff1a;元宇宙-秩沅 &#x1f468;?&#x1f4bb; hallo 歡迎 點贊&#x1f44d; 收藏? 留言&#x1f4dd; 加關注?! &#x1f468;?&#x1f4bb; 本文由 秩沅 原創 &#x1f468;?&#x1f4bb; 收錄于專欄&#xff1a;uni…

TCP消息傳輸可靠性保證

TCP鏈接與斷開 -- 三次握手&四次揮手 三次握手 TCP 提供面向有連接的通信傳輸。面向有連接是指在數據通信開始之前先做好兩端之間的準備工作。 所謂三次握手是指建立一個 TCP 連接時需要客戶端和服務器端總共發送三個包以確認連接的建立。在socket編程中&#xff0c;這一…

算法模版,今天開始背

二分查找算法 int left_bound(int[] nums, int target) {int left 0, right nums.length - 1;// 搜索區間為 [left, right]while (left < right) {int mid left (right - left) / 2;if (nums[mid] < target) {// 搜索區間變為 [mid1, right]left mid 1;} else if …

ubuntu更換國內apt源

ubuntu必備操作 1 更換apt鏡像源 備份鏡像 cp /etc/apt/sources.list /etc/apt/sources.list.bak查看自己ubuntu版本 # 查看自己的codename #查看自己的ubuntu版本[注意關注&#xff1a;DISTRIB_CODENAME&#xff0c;發行代號] cat /etc/*release# DISTRIB_CODENAMEcosmic …

面試熱題(合并K個升序鏈表)

給定一個鏈表數組&#xff0c;每個鏈表都已經按升序排列。 請將所有鏈表合并到一個升序鏈表中&#xff0c;返回合并后的鏈表。 輸入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 輸出&#xff1a;[1,1,2,3,4,4,5,6] 解釋&#xff1a;鏈表數組如下&#xff1a; [1->4->5,1…

【軟件工程】面向對象方法-RUP

RUP&#xff08;Rational Unified Process&#xff0c;統一軟件開發過程&#xff09;。 RUP特點 以用況驅動的&#xff0c;以體系結構為中心的&#xff0c;迭代增量式開發 用況驅動 用況是能夠向用戶提供有價值結果的系統中的一種功能用況獲取的是功能需求 在系統的生存周期中…

解決在vue中img標簽不顯示圖片的問題

在vue中, 經常會遇到img標簽不展示的問題, 本人遇到兩種, 都是因為webpack打包, 導致找不到路徑, 所以不現實, 總結幾個可以解決本地圖片路徑顯示不出來的問題&#xff1a; 1.把圖片放在src同級的static文件夾下。 2.把圖片放在cdn上&#xff0c;把網絡地址存在imgUrl里&#x…

RabbitMQ: 詳解、使用教程和示例

RabbitMQ: 詳解、使用教程和示例 什么是 RabbitMQ&#xff1f; RabbitMQ 是一個開源的消息代理&#xff08;Message Broker&#xff09;軟件&#xff0c;它實現了高級消息隊列協議&#xff08;AMQP&#xff09;&#xff0c;用于在應用程序之間進行異步消息傳遞。它允許應用程…

uni-app日期選擇器

寫個簡單的日期選擇器&#xff0c;還沒搞樣式&#xff0c;所以有點丑 大概長這樣吧 首先是這個picker選擇器&#xff0c;mode選擇日期&#xff0c;end是寫一個范圍前日期&#xff0c;:end就是這個日期是動態變化的&#xff0c;還有change函數 <template><view>&l…

【pinia】Pinia入門和基本使用:

文章目錄 一、 什么是pinia二、 創建空Vue項目并安裝Pinia1. 創建空Vue項目2. 安裝Pinia并注冊 三、 實現counter四、 實現getters五、 異步action六、 storeToRefs保持響應式解構七、基本使用&#xff1a;【1】main.js【2】store》index.js【3】member.ts 一、 什么是pinia P…

Python:列表、元組、集合、字典,數據類型之間的 5 個差異

Python&#xff1a;列表、元組、集合、字典&#xff0c;數據類型之間的 5 個差異 1. 相同點2. 不同點2.1 排序2.2 索引2.3 可變性2.5 允許的類型2.4 允許重復 源碼 這篇博客將介紹列表、元組、集合、字典&#xff08;lists, tuples, sets, and dictionaries&#xff09;數據類型…

6.0 Python 使用函數裝飾器

裝飾器可以使函數執行前和執行后分別執行其他的附加功能&#xff0c;這種在代碼運行期間動態增加功能的方式&#xff0c;稱之為"裝飾器"(Decorator)&#xff0c;裝飾器的功能非常強大&#xff0c;裝飾器一般接受一個函數對象作為參數&#xff0c;以對其進行增強&…

安達發APS|生產計劃排產軟件助力加工制造業智能化轉型

隨著全球經濟一體化的不斷深入&#xff0c;市場競爭日益激烈&#xff0c;加工制造企業面臨著巨大的生存壓力。在這種情況下&#xff0c;企業對于生產計劃的精細化管理需求日益迫切。為了適應這一市場需求&#xff0c;安達發推出了專門針對加工企業的APS生產計劃排產軟件&#x…

新一代構建工具 maven-mvnd

新一代構建工具 maven-mvnd mvnd的前世今生下載安裝 mvndIDEA集成 mvnd的前世今生 maven 作為一代經典的構建工具&#xff0c;流行了很多年&#xff0c;知道現在依然是大部分Java項目的構建工具的首選&#xff1b;但隨著項目復雜度提高&#xff0c;代碼量及依賴庫的增多使得ma…

簡單易懂的 Postman Runner 參數自增教程

目錄 什么是 Postman Runner&#xff1f; Postman Runner 如何實現參數自增&#xff1f; 步驟一&#xff1a;設置全局參數 步驟二&#xff1a;將全局參數帶入請求參數 步驟三&#xff1a;實現參數自增 資料獲取方法 什么是 Postman Runner&#xff1f; Postman Runner 是…

Python爬蟲(1)一次性搞定Selenium(新版)8種find_element元素定位方式

selenium中有8種不錯的元素定位方式&#xff0c;每個方式和應用場景都不一樣&#xff0c;需要根據自己的使用情況來進行修改 8種find_element元素定位方式 1.id定位2.CSS定位3.XPATH定位4.name定位5.class_name定位6.Link_Text定位7.PARTIAL_LINK_TEXT定位8.TAG_NAME定位總結 …

【第一階段】kotlin中反引號中的函數名特點

在kotlin中可以直接中文定義函數&#xff0c;使用反引號進行調用 eg: fun main() {2023年8月9日定義的函數(5) }private fun 2023年8月9日定義的函數(num:Int){println("反引號的用法$num") }執行結果 在Java中is,in可以定義方法&#xff0c;但是在kotlin中is,in是…