代理模式 PROXY Surrogate 結構型 設計模式(十四)

来源:https://www.cnblogs.com/noteless/archive/2018/12/05/10070057.html
-Advertisement-
Play Games

代理模式是一種很常用的模式,JDK也內置了對於代理的支持,動態代理,本文對代理模式進行了介紹,意圖,結構,java實現,對靜態代理和動態代理進行了分析,並且給出了代碼示例,並且介紹了CGLIB的使用。 ...


代理模式 PROXY 別名Surrogate

意圖

為其他的對象提供一種代理以控制對這個對象的訪問。 代理模式含義比較清晰,就是中間人,中介公司,經紀人... 在電腦程式中,代理就表示一個客戶端不想或者不能夠直接引用一個對象 而代理對象可以在客戶端和目標對象之間起到中介的作用

結構

代理模式的根本在於隔離,如下圖所示,間接訪問 image_5c074938_56cd 代理對象如何能夠真的代理真實對象? 在Java語言中,看起來像的一個方式就是實現同一介面 image_5c074938_5f89   代理角色和真實對象角色擁有共同的抽象類型,他們擁有相同的對外介面request()方法 ProxySubject內部擁有一個RealSubject 你應該能感覺到組合模式的思想-----他們都是Subject,屬於同一個Component 對外有一致的介面 抽象主題角色Subject 聲明瞭真實主題和代理主題的共同介面,任何使用真實主題的地方,都可以使用代理主題 代理主題角色ProxySubject 代理主題角色內部含有對真實對象的引用,從而可以在任何時候操作真實主題 代理主題提供與真實主題的相同的介面,以便任何時刻,都可以替代真實主題 而且,代理主題還可以在真實主題執行前後增加額外的處理,比如:經紀人要先收下費~ 真實主題角色RealSubject 被代理的真實主題對象,真正工作的是他,比如經紀人總不會站在舞臺上去~

示例代碼

Subject 抽象角色 定義了真正的處理請求 的request()方法 
package proxy;
public interface Subject {
void request();
}
RealSubject真實主題角色,實現了處理請求的方法
package proxy;
public class RealSubject implements Subject {
@Override
public void request() {
    System.out.println("realSubject process request....");
}
}
Proxy代理角色 實現了request()方法,用於替代真實主題,內部調用真實主題完成請求 並且額外的提供了pre和after操作
package proxy;
public class Proxy implements Subject{
    private Subject realSubject;
    @Override
    public void request() {
        preRequest();
    realSubject.request();
        afterRequest();
    }
     
    public Proxy(Subject realSubject){
        this.realSubject = realSubject;
    }
    public void preRequest(){
        System.out.println("pre request do sth....");
    }
     
    public void afterRequest(){
        System.out.println("after request do sth....");
    }
}
測試類
package proxy;
public class Test {
/**請求subject執行請求
* @param subject
*/
public static void askForSth(Subject subject){
    subject.request();
    System.out.println("################");
 }
   
public static void main(String[] args){
     Subject real = new RealSubject();
     Subject proxy = new Proxy(real);
     askForSth(proxy);
     askForSth(real);
    }
}
定義了真實對象,也定義了一個代理對象 查看他們分別處理請求的結果 image_5c074938_777b   從下麵的時序圖中,能更好的感受到“間接”的感覺 在真正調用真實對象方法前,需要先執行preRequest方法 真實對象方法調用後,在執行afterRequest方法 image_5c074939_77de

代理實現

代理的實現分類有兩種,靜態代理和動態代理 前面形式描述的代理,就是靜態代理 在編譯時期,就已經編寫生成好了代理類的源代碼,程式運行之前class文件就已經生成了 這種按照我們上面模式編寫了代理類和真實類的形式就是 靜態代理 靜態代理經常被用來對原有邏輯代碼進行擴展,原有的邏輯不需要變更,但是可以增加更多的處理邏輯 但是,但是如果有很多的對象需要被代理怎麼辦? 如果按照靜態代理的形式,那麼將會出現很多的代理類,勢必導致代碼的臃腫。 所以後來出現了動態代理

JDK代理機制

所謂動態代理,按照字面意思就是動態的進行代理,動態相對於靜態的含義是不需要事先主動的創建代理類,可以在運行時需要的時候,動態的創建一個代理類。 動態代理的動態關鍵在於代理類的動態生成,不需要我們實現創建,從class文件的角度來看的話,是與靜態代理一樣的,仍舊有一個代理類的Class文件 在Java中提供了內置的動態代理的支持。 Java在java.lang.reflect包中提供了三個核心 ProxyInvocationHandlerMethod  可以用於動態代理的使用   Java動態代理簡單示例
package proxy.MyDynamicProxy;
public interface Subject {
void doSth();
}
package proxy.MyDynamicProxy;
public class RealSubject implements Subject {
@Override
public void doSth() {
System.out.println("real Object do something...");
}
}
package proxy.MyDynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {
private Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy do something....");
return method.invoke(realSubject, args);
}
}
package proxy.MyDynamicProxy;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
Subject proxy = (Subject) Proxy
.newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject));
proxy.doSth();
}
}
測試結果為: image_5c074939_41fd   動態代理到底都做了什麼? 對於靜態代理,我們有一個RealSubject,以及他的超介面Subject Subject定義了方法,RealSubject實現了方法。 然後我們創建了代理類,這個代理類實現了Subject介面,並且將新增的邏輯添加進來,然後通過代理類進行方法調用。    在上面的例子中,RealSubject,以及他的超介面Subject含義不變,與靜態代理中的邏輯一樣。 然後我們創建了一個調用處理器DynamicProxyHandler 實現了 InvocationHandler介面 該介面只有一個方法invoke   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 他有三個參數 proxy - 在其上調用方法的代理實例 method - 對應於在代理實例上調用的介面方法的 Method 實例。Method 對象的聲明類將是在其中聲明方法的介面,該介面可以是代理類賴以繼承方法的代理介面的超介面。 args - 包含傳入代理實例上方法調用的參數值的對象數組,如果介面方法不使用參數,則為 null。基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。   最後通過Java提供的代理機制創建了一個代理     Subject proxy = (Subject) Proxy         .newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject)); 核心就是newProxyInstance方法,他創建了一個實現了Subject介面的代理類 public static Object newProxyInstance(ClassLoader loader,                                       Class<?>[] interfaces,                                       InvocationHandler h)                                throws IllegalArgumentException 這個方法也有三個參數 loader - 定義代理類的類載入器 interfaces - 代理類要實現的介面列表 h - 指派方法調用的調用處理程式   為什麼需要這三個參數呢? 首先,Proxy.newProxyInstance幫你動態的創建方法,肯定要有一個類載入器,上面的示例中我們直接使用的測試類的類載入,這個一般是應用程式  類載入器 再者,動態代理與靜態代理一樣,需要實現同樣的介面,那你實現了哪些介面呢?所以你得把介面列表告訴我 最後,你希望有哪些處理呢?你要把處理器給我   proxy.doSth();執行時,會將當前代理實例,以及當前方法,以及當前方法的參數傳遞給invoke方法,所以就完成代理的功能。   再來重頭理一下:
  1. 如同靜態代理,需要被代理的對象RealSubject,以及他的超介面Subject
  2. 需要實現InvocationHandler介面創建一個處理器,新增加的方法邏輯封裝在invoke方法中
  3. Proxy.newProxyInstance創建代理實例
  4. 使用創建的代理實例執行方法
簡言之,動態代理與靜態代理一模一樣,差別就在於不用你事先去自己主動地創建一個代理類 靜態的時候編寫了代理類,然後編譯為class然後需要時被載入到JVM,然後調用 動態是運行時在需要的時候,直接生成class文件   依照上面的步驟流程,你就可以藉助於Java的機制實現動態代理 但是你會發現,Proxy.newProxyInstance方法的參數需要一個 Class<?>[] interfaces,這意味著什麼?這意味著被代理的對象必須實現一個介面 如果被代理的對象不曾實現任何介面怎麼辦? 給每個被代理的對象增加一個標記介面(形式介面)?如果只是為了使用JDK的動態代理實現,而添加了無意義的介面這是否妥當?

CGLIB

還有另外一種形式的動態代理CGLIB 需要兩個Jar   image_5c074939_55fa
package proxy.cglib;
public class RealSubject{
public void doSth() {
System.out.println("realSubject process request....");
}
}
package proxy.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyHandler implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("before do something...");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("after do something...");
return object;
}
}
package proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MyHandler());
RealSubject subject = (RealSubject)enhancer.create();
subject.doSth();
}
}
在這個示例中,不再需要介面,僅僅只有一個真是對象RealSubject 實現了一個處理器 MyHandler 繼承自 MethodInterceptor,實現了intercept方法 在測試客戶端中,通過四個步驟創建了代理對象,然後藉助於代理對象執行 image_5c074939_666   從    enhancer.setSuperclass(RealSubject.class);這一句或許猜得到,CGLIB不依賴於介面,而是代理類繼承了真實主題類   流程 真實主題對象RealSubject是必不可少的,否則代理模式就沒有意義了 類似JDK的代理模式,處理器也是解耦的,在CGLIB中藉助於MethodInterceptor介面約定,這一步要做的事情的本質與InvocationHandler並沒有什麼太多不同---封裝附加的處理邏輯 藉助於Enhancer用來組裝處理創建邏輯,並且創建代理類 setSuperclass設置需要繼承的類(也就是被代理的類) setCallback設置回調函數 create創建真正的代理對象。   CGLIB採用繼承的機制,如果一個類是final的怎麼辦?那就歇菜了

JDK代理機制與CGLIB對比

目前到JDK8 據說性能已經優於CGLIB了 JDK機制不需要第三方Jar,JDK預設集成,CGLIB需要引入第三方Jar包 JDK需要依賴真實主題對象實現介面,CGLIB則不需要,CGLIB繼承了真實主題 CGLIB雖然不依賴真實主題實現介面,但是被代理的類不能為final,那樣的類是無法繼承的 通常的做法是如果實現了介面,那麼使用JDK機制,如果沒有實現介面,使用CGLIB

代理用途分類

代理模式的根本在於隔離,“間接”,只要隔離,間接,那麼就可以隱藏真實對象,並且增加額外的服務,優化,管理等 比如 隱藏了真實的對象,比如你通過中介租房子,可能到期也沒見過房東   提供了代理層,可以提供更多服務 比如買賣房屋通過中介可以節省你合同的審校工作,很多人不懂合同中暗藏的貓膩   隱藏真實對象,自然能夠起到一定的保護作用,避免了直接接觸 比如去學校見孩子,需要先經過老師同意   通過代理,也相當於有一個管家,可以管理外界對真實對象的接觸訪問 比如,真實對象是電腦,管家類軟體相當於代理,可以限制小孩子對電腦的使用時長   圍繞著代理帶來的特點“隱藏真實對象,並且增加額外的服務,優化,限制” 在多種場景下,延伸出來一些分類 遠程代理 Remote
為一個位於不同的地址空間的對象提供一個局域代表對象,這個不同的地址空間可以是本機器的,也可以是另一臺機器的
虛擬代理 Virtual 根據需要創建一個資源消耗較大的對象,使得此對象只在需要時才會被真正創建 保護代理 Protect or Access 控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用許可權 Cache代理 為一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共用這些結果  防火牆代理 Firewall 保護目標,防止惡意行為 同步代理 Synchronization
使幾個用戶能夠同時使用一個對象而沒有衝突

智能引用 Smart Reference 當一個對象被引用時,提供一些額外的操作,比如將對象調用次數記錄下來   很顯然,這些分類其實只是代理的不同應用場景,以後可能還會有更多的分類出來 但是永遠也脫離不了代理的“隔離”“間接”的根本核心。

總結

代理角色雖然是真實角色的“代理人”,雖然代理角色內部依賴真實角色 但是真實角色可以完全脫離代理人,單獨出現 比如上面示例中的

        askForSth(proxy);

        askForSth(real); 

只不過,通過代理角色會有不同的效果   代理人只是會“幫助”解決他能解決的問題,它能提供的服務,他做不了的事情 比如經紀人不會出唱片,對於出唱片的任務還是會委托給真實角色 現實世界中,我們通常說真實角色委托代理角色,比如,房東找中介 在程式世界中,通常卻說代理角色將任務委托給真實角色,委托與被委托都是相對的 要看你到底是站在什麼視角看待問題,無所謂~   再次強調,代理模式的重點在於增加對真實受訪對象的控制,也可以增加額外的服務。 原文地址:代理模式 PROXY Surrogate 結構型 設計模式(十四)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 效果圖: html: <div class='site_bar'>首頁</div> css: .site_bar{ background-image : linear-gradient(red,red); background-position : center bottom; background ...
  • js獲取選中日期的當周的周一和周日 ...
  • 如果這是第二次看到我的文章,歡迎右側掃碼訂閱我喲~ 👉 本文長度為5269字,預計讀完需1.2MB流量,建議閱讀14分鐘。 可能你在網上看過不少「限流」相關的文章,但是z哥的這篇可能是最全面,最深入淺出的一篇了(容我飄幾秒~)。 開個玩笑,希望你能收穫一些增量價值就好~。 之前有瞭解到z哥的一部分 ...
  • 系統架構設計師-軟體水平考試高級-理論-電腦網路。其中涉及TCP/IP協議族,網路規劃與設計,網路接入,網路存儲,綜合佈線,物聯網,雲計算等。 ...
  • 模板方法模式(Template Method Pattern)是一種簡單的、常見的且應用非常廣泛的模式。 定義: 定義一個操作中演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。 模板方法模式的類圖如下所示。 模板方法模式涉及兩個角色: 抽象模板( ...
  • 海康&大華&DSS獲取RTSP 實時流 海康:rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream說明:username: 用戶名。例如admin。password: 密碼。例如12345。ip: ...
  • 命令模式(Command Pattern)又稱為行動(Action)模式或交易(Transaction)模式。 定義: 將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數化,對請求排隊或記錄請求日誌,可以提供命令的撤銷和恢復功能。 命令模式類圖如下所示。 命令模式中有如下4個角色。 命令( ...
  • 搬到小機房後終於能用VSCode啦(~~沒錯以前的系統是xp~~) 但是這東西比Dev難搞多了qwq,簡單記一下自己的DIY歷程吧(~~不然全搞炸就涼了~~) 設置語言為中文 可以直接下載插件 讓VSCode支持編譯C++程式 首先要有MingW,一個很simple的方法是直接把DevC++的Min ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...