主頁 > 軟體設計 > 代理模式

代理模式

2020-09-12 08:39:03 軟體設計

文章首發于我的個人博客,歡迎訪問:https://blog.itzhouq.cn/proxy

在代理模式(Proxy Pattern)中,一個類代表另一個類的功能,這種型別的設計模式屬于結構型模式,在代理模式中,我們創建具有現有物件的物件,以便向外界提供功能介面,代理模式的優勢是實作了無侵入的代理擴展,也就是方法的增強;讓你可以在不用修改原始碼的情況下,增強一些方法,

為什么要學習代理模式呢?因為 Spring AOP 的底層就是代理模式,

現在模擬一個場景:房東需要出租自己的房子,

實際落地時候將出租這個動作抽象為介面:

/**
 * 租房的介面
 */
public interface Rent {
    public void rent();
}

房東(Host)實作了這個介面,執行一個租房的方法,

/**
 * 房東
 */
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房東要出租房子");
    }
}

這個例子在日常生活中很常見,但是我們考慮到實際情況,并不是所有的房東都有那么多自由的時間和資源,不上班就為了把自己的房子租出去的,為了省事他們都會借助于第三方公司做這個事情,就是我們常說的中介,這里的中介就是這個代理物件,

1、靜態代理

為什么要加一個代理物件呢?因為代理物件可以做一些額外的操作,比如這里中介除了可以幫房東租房子以外,還能帶客戶看房子,簽合同,收中介費等,

上面的介面和類不需要變動,現在增加代理的功能,

/**
 * 代理類:其作用是幫房東租房子
 */
public class Proxy implements Rent {
    private Host host;

    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }

    @Override
    public void rent() {
        seeHouse();
        host.rent();
        fare();
    }

    public void seeHouse() {
        System.out.println("中介帶你看房子");
    }
    public void fare() {
        System.out.println("中介收中介費");
    }
}

模擬租房的程序:

/**
 * 模擬租房的程序
 */
public class Client {
    public static void main(String[] args) {
        // 房東要租房子
        Host host = new Host();
        // 代理角色:中介,幫房東租房子,除此之外提供一些額外的服務
        Proxy proxy = new Proxy(host);
        // 你不用面對房東,直接找中介即可
        proxy.rent();
    }
}

運行的結果:

中介帶你看房子
房東要出租房子
中介收中介費

可以看到代理類對被代理的物件進行了增強,

角色分析:

  • 抽象角色:一般會使用介面或者抽象類解決
  • 真實角色:被代理的物件
  • 代理角色:代理真實角色,代理真實角色后,我們一般會做一些附屬操作
  • 客戶:訪問代理物件的人,

2、靜態代理舉例

再看一個實際開發中遇到的問題,有個介面UserService,其實作類 UserServiceimpl中有很多方法,現在有個需求,需要在執行的方法前面添加日志,知道哪個方法執行了,但是不改變原有的UserServiceimpl實作類,

如果直接修改這個實作類會有以下幾個問題:

  • 可能這個實作類不能讓你直接修改
  • 這個實作類可以修改,但是修改的話有增加 BUG 的風險
  • 這個實作類可以修改,但是實作類中方法很多,需要添加大量重復的代碼

這個時候代理模式就能排上用場了,

介面:

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

實作類:

/**
 * 真實物件
 */
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("增加了一個用戶");
    }

    @Override
    public void delete() {
        System.out.println("洗掉了一個用戶");
    }

    @Override
    public void update() {
        System.out.println("更新了一個用戶");
    }

    @Override
    public void query() {
        System.out.println("查詢了一個用戶");
    }
}
/**
 * 模擬用戶的操作
 */
public class UserAction {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.add();
    }
}

現在模擬添加日志的需求:

添加一個代理類,實作 UserService介面,注入UserServiceImpl,添加日志的方法:

public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;
    public UserServiceProxy(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    // 日志方法
    public void log (String msg) {
        System.out.println("執行了" + msg + "方法");
    }
}

模擬用戶操作:

/**
 * 模擬用戶的操作
 */
public class UserAction {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
//        userService.add();
        UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
        userServiceProxy.add();
    }
}

運行結果:

執行了add方法
增加了一個用戶

通過這個例子,可以很好的理解靜態代理模式的使用場景和使用方法,

3、動態代理

  • 動態代理和靜態代理角色一樣
  • 動態代理的代理類是動態生成的,不是我們直接寫好的,
  • 動態代理分為兩大類:基于介面的動態代理和基于類的動態代理
    • 基于介面 --- JDK 動態代理
    • 基于類 --- cglib
    • Java 位元組碼 --- javassist

3、1 JDK 自帶的動態代理

  • java.lang.reflect.Proxy:生成動態代理類和物件;提供了靜態方法,可以創建動態代理類和實體,
  • java.lang.reflect.InvocationHandler(處理器介面):可以通過invoke方法實作對真實角色的代理訪問,

每次通過 Proxy 生成的代理類物件都要指定對應的處理器物件,

下面的代碼演示了,如何通過 JDK 的動態代理生成一個特定代理物件,進而對被代理物件進行增強,

介面:Subject.java

public interface Subject {
    public int sellBooks();

    public String speak();
}

真實物件:RealSubject.java

public class RealSubject implements Subject {
    @Override
    public int sellBooks() {
        System.out.println("賣書");
        return 1;
    }

    @Override
    public String speak() {
        System.out.println("說話");
        return "張三";
    }
}

處理器物件:MyInvocationHandler.java

等下就是使用這個處理器對真實物件的方法進行增強,

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 處理器物件
 */
public class MyInvocationHandler implements InvocationHandler {
    /**
     * 因為需要處理真實物件,需要把真實角色傳進來
     */
    Subject realSubject;

    public MyInvocationHandler(Subject realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * @param proxy     代理類
     * @param method    正在呼叫的方法
     * @param args      方法的引數
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("呼叫代理類");

        if (method.getName().equalsIgnoreCase("sellBooks")) {
            int invoke = (int) method.invoke(realSubject, args);
            System.out.println("呼叫的是賣書的方法");
            return invoke;
        } else {
            String string = (String) method.invoke(realSubject, args);
            System.out.println("呼叫的是說話的方法");
            return string;
        }
    }
}

呼叫端進行測驗:Main.java

import java.lang.reflect.Proxy;
/**
 * 呼叫類
 */
public class Main {
    public static void main(String[] args) {
        // 真實物件
        Subject realSubject = new RealSubject();
        // 根據 真實物件 創建一個 處理器物件
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realSubject);

        // 代理物件:這個代理物件是通過 Proxy 類的靜態方法,動態生成的
        Subject proxyClass = (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, myInvocationHandler);

        // proxyClass.sellBooks();

        proxyClass.speak();
    }
}

執行結果:

呼叫代理類
說話
呼叫的是說話的方法

從結果可以看到通過生成代理物件,對真實物件的方法進行了增強,

值得注意的是,這個處理類物件myInvocationHandler 其實是Proxy.newProxyInstance方法的一個引數,可以寫成匿名內部類,這個可以看我之前寫的筆記增強一個物件的方法的三種方式,那里就是這么寫的,

3、2 Cglib 動態代理

Cglib 動態代理是針對代理的類,動態生產一個子類,然后子類覆寫代理類中的方法,如果是final 或是 private 修飾的,則不會被重寫,Cglib 是一個功能強大,高性能的代碼生成包,它為沒有實作介面的類提供代理,為 JDK 的動態代理提供了很好的補充,通常可以使用 Java 的動態代理創建代理,但當要代理的類沒有實作介面或為了更好的性能,Cglib 是一個好的選擇,

Cglib 作為一個開源專案,其代碼托管在在 GitHub ,地址為: https://github.com/cglib/cglib ,

使用 Cglib 需要匯入相關依賴或者 jar 包,我這里匯入 jar 包,下載地址: https://github.com/cglib/cglib/releases/tag/RELEASE_3_3_0 ,除此之外,Cglib正常與運行還需要asm.jar的支持( cglib 底層使用位元組碼處理框架ASM,來轉換位元組碼并生成新的類 ),下載地址: https://mvnrepository.com/artifact/org.ow2.asm/asm/7.2 ,

注意:如果沒有匯入asm的依賴會拋出例外:

Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type

下面通過一個簡單的例子來說明其使用方式:

需要被代理的類:

package cn.itzhouq.proxy.cglib;

/**
 * 被代理類
 */
public class Engineer {
    // 可以被代理
    public void eat() {
        System.out.println("工程師正在吃飯");
    }

    // final 方法不會被生成的子類覆寫
    public final void work() {
        System.out.println("工程師正在作業");
    }

    // private 方法不會被生成的子類覆寫
    private void play () {
        System.out.println("工程師正在玩游戲");
    }
}

可以看到這個被代理的類是沒有實作介面的,

Cglib 代理類:

package cn.itzhouq.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * CGLIB 代理類
 */
public class CglibProxy implements MethodInterceptor {
    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("###   before invocation");
        Object result = method.invoke(target, objects);
        System.out.println("###   end invocation");
        return result;
    }

    public static Object getProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        // 設定需要代理的物件
        enhancer.setSuperclass(target.getClass());
        // 設定代理人
        enhancer.setCallback(new CglibProxy(target));
        return enhancer.create();
    }
}

測驗方法:

package cn.itzhouq.proxy.cglib;

/**
 * 測驗方法
 */
public class CglibMainTest {
    public static void main(String[] args) {
        // 生成 Cglib 代理類
        Engineer engineerProxy = (Engineer) CglibProxy.getProxy(new Engineer());
        // 呼叫相關方法
        engineerProxy.eat();
        engineerProxy.work();
        // engineerProxy.play(); // 該代理物件中沒有該方法
    }
}

運行結果:

###   before invocation
工程師正在吃飯
###   end invocation
工程師正在作業

通過這種代理方式,也可以對方法進行增強,被 final修飾的方法不會被生成的代理類(也是子類)覆寫,被private修飾的方法壓根兒不會被代理類繼承,

關于 Cglib 的原理,可以參考這篇文章: https://www.runoob.com/w3cnote/cglibcode-generation-library-intro.html ,

4、代理模式和裝飾器模式的區別

兩者都是對類的方法進行擴展,但是裝飾器模式強調的是增強自身,在被裝飾之后你能夠在被增強的類上使用增強后的功能,增強后你還是你,只不過能力更強了而已;而代理模式則強調的要讓別人幫你去做一些本身與你業務沒有太多關系的職責(記錄日志、設定快取),代理模式是為了實作物件的控制 ,因為被代理的物件往往難以直接獲得或者其內部不想暴露出來,

關于這個細節,可以這篇知憾訓答,作者舉的例子很形象,

Java中“裝飾模式”和“代理模式”有啥區別? - 知乎 https://www.zhihu.com/question/41988550/answer/462204684

5、代理模式的優缺點

優點:

  • 可以使真實角色的操作更加純粹!不用去關注一些公共的業務
  • 公共業務交給代理角色!實作了業務的分工
  • 公共業務發生擴展的時候,方便集中管理

缺點:

  • 一個真實物件就會產生一個代理物件,代理量會翻倍,

參考文章

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/12577.html

標籤:設計模式

上一篇:設計模式-職責鏈模式(ChainOfResponsibility)

下一篇:【面試題】Java單例設計模式-餓漢式列舉(enum)單例

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more