Java基礎(chǔ)之代理模式及動態(tài)代理

  1. 代理模式
  2. 代理模式角色定義
  3. 靜態(tài)代理
    3.1 靜態(tài)代理實例
    3.2 靜態(tài)代理的缺點
  4. 動態(tài)代理
    4.1 基于JDK原生動態(tài)代理實現(xiàn)

1. 代理模式

為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下冬殃,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標(biāo)對象之間起到中介的作用。

通過代理模式可以做到:

  1. 隱藏委托類的具體實現(xiàn)。
  2. 實現(xiàn)客戶與委托類的解耦何陆,在不改變委托類代碼的情況下添加一些額外的功能(日志此迅、權(quán)限)等。

2. 代理模式角色定義

三類對象:

  • Subject(抽象主題角色):定義代理類和真實主題的公共對外方法戈二,也是代理類代理真實主題的方法。
  • RealSubject(真實主題角色):真正實現(xiàn)業(yè)務(wù)邏輯的類喳资。
  • Proxy(代理主題角色):用來代理和封裝真實主題觉吭。
代理模式角色

3. 靜態(tài)代理

靜態(tài)代理是指代理類在程序運行前就已經(jīng)存在。

3.1 靜態(tài)代理實例

首先定義一組接口Sell仆邓,用來提供廣告和銷售等功能鲜滩。然后提供Vendor類(廠商,被代理對象)和Shop(超市节值,代理類)徙硅,它們分別實現(xiàn)了Sell接口。

Sell接口定義如下:

package javaBasic.proxy;

/**
 * 首先定義一組接口Sell察署,用來提供廣告和銷售等功能闷游。
 */
public interface Sell {

    /**
     * 出售
     */
    void sell();

    /**
     * 廣告
     */
    void ad();

}

Vendor類定義如下:

package javaBasic.proxy;

public class Vendor implements Sell {
    @Override
    public void sell() {
        System.out.println("Shop sell goods");
    }

    @Override
    public void ad() {
        System.out.println("Shop advert goods");
    }
}

Shop類定義如下:

package javaBasic.proxy;

/**
 * 其中代理類Shop通過聚合的方式持有了被代理類Vendor的引用,
 * 并在對應(yīng)的方法中調(diào)用Vendor對應(yīng)的方法贴汪。
 * 在Shop類中我們可以新增一些額外的處理脐往,比如篩選購買用戶、記錄日志等操作扳埂。
 */
public class Shop implements Sell{

    private Sell sell;

    public Shop(Sell sell){
        this.sell = sell;
    }

    @Override
    public void sell() {
        System.out.println("代理類Shop, 處理sell");
        sell.sell();
    }

    @Override
    public void ad() {
        System.out.println("代理類Shop, 處理ad");
        sell.ad();
    }
}

其中代理類Shop通過聚合的方式持有了被代理類Vendor的引用业簿,并在對應(yīng)的方法中調(diào)用Vendor對應(yīng)的方法。在Shop類中我們可以新增一些額外的處理阳懂,比如篩選購買用戶梅尤、記錄日志等操作柜思。

客戶端使用代理類的方式:

package javaBasic.proxy;

/**
 * 針對客戶看到的是Sell接口提供了功能,而功能又是由Shop提供的巷燥。
 * 我們可以在Shop中修改或新增一些內(nèi)容赡盘,而不影響被代理類Vendor。
 */
public class StaticProxy {

    public static void main(String[] args) {
        // 供應(yīng)商---被代理類
        Vendor vendor = new Vendor();

        // 創(chuàng)建供應(yīng)商的代理類Shop
        Sell sell = new Shop(vendor);

        // 客戶端使用時面向的是代理類Shop缰揪。
        sell.ad();
        sell.sell();

    }

}

在上述代碼中陨享,針對客戶看到的是Sell接口提供了功能,而功能又是由Shop提供的钝腺。我們可以在Shop中修改或新增一些內(nèi)容抛姑,而不影響被代理類Vendor。

3.2 靜態(tài)代理的缺點

  1. 當(dāng)需要代理多個類時艳狐,代理對象要實現(xiàn)與目標(biāo)對象一致的接口定硝。要么,只維護一個代理類來實現(xiàn)多個接口毫目,但這樣會導(dǎo)致代理類過于龐大蔬啡。要么,新建多個代理類蒜茴,但這樣會產(chǎn)生過多的代理類星爪。
  2. 當(dāng)接口需要增加、刪除粉私、修改方法時,目標(biāo)對象與代理類都要同時修改近零,不易維護诺核。

4. 動態(tài)代理

動態(tài)代理是指代理類在程序運行時進行創(chuàng)建的代理方式。這種情況下久信,代理類并不是在Java代碼中定義的窖杀,而是在運行時根據(jù)Java代碼中的“指示”動態(tài)生成的。
相比于靜態(tài)代理裙士,動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理入客,而不用修改每個代理類的函數(shù)。

4.1 基于JDK原生動態(tài)代理實現(xiàn)

實現(xiàn)動態(tài)代理通常有兩種方式:JDK原生動態(tài)代理和CGLIB動態(tài)代理腿椎。這里桌硫,我們以JDK原生動態(tài)代理為例。

JDK動態(tài)代理主要涉及兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler啃炸。
InvocationHandler接口定義了如下方法:

/**
 * 調(diào)用處理程序
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

實現(xiàn)了該接口的中介類用做“調(diào)用處理器”铆隘,當(dāng)調(diào)用代理類對象的方法時,這個調(diào)用會直接跳轉(zhuǎn)到invoke方法之中南用,代理類對象作為proxy參數(shù)傳入膀钠,參數(shù)method標(biāo)識了具體調(diào)用的是代理類的哪個方法掏湾,args為該方法的參數(shù)。這樣對代理類中的所有方法的調(diào)用都會變?yōu)閷nvoke的調(diào)用肿嘲,可以在invoke方法中添加統(tǒng)一的處理邏輯(也可以根據(jù)method參數(shù)對不同的代理類方法做不同的處理)融击。

Proxy類用來實例化指定代理對象所關(guān)聯(lián)的調(diào)用處理器。

下面以添加日志為例演示動態(tài)代理雳窟。
首先是建立LogHandler類:

package javaBasic.proxy;

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

public class LogHandler implements InvocationHandler {

    Object target; //被代理的對象, 實際的方法執(zhí)行者

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target,args);
        after();
        return result;
    }


    //調(diào)用invoke方法之前執(zhí)行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }

    //調(diào)用invoke方法之后執(zhí)行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

客戶端編寫程序使用動態(tài)代理代碼如下:

package javaBasic.proxy;

import java.lang.reflect.Proxy;

public class DynamicProxyMain {

    public static void main(String[] args) {
        //創(chuàng)建中介類實例
        LogHandler logHandler = new LogHandler(new Vendor());

        //獲取代理類實例
        Sell sell = (Sell) Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler);

        //通過代理類對象調(diào)用代理類方法尊浪,實際上會轉(zhuǎn)到invoke方法(LogHandler之中的sell方法)調(diào)用
        //這個代理類之中的方法調(diào)用是通過反射實現(xiàn)的
        sell.sell();
        sell.ad();
    }

}

執(zhí)行后結(jié)果(已經(jīng)成功為我們的被代理類統(tǒng)一添加了執(zhí)行方法之前和執(zhí)行方法之后的日志):

log start time [Fri Aug 21 09:39:04 AEST 2020] 
Shop sell goods
log end time [Fri Aug 21 09:39:04 AEST 2020] 
log start time [Fri Aug 21 09:39:04 AEST 2020] 
Shop advert goods
log end time [Fri Aug 21 09:39:04 AEST 2020] 

動態(tài)代理的原理:

執(zhí)行上述方法后會生成一個$Proxy0.class類文件。該類繼承了Proxy類涩拙,并且實現(xiàn)了被代理的所有接口际长,以及equals、hashCode兴泥、toString等方法工育。

由于動態(tài)代理類繼承了Proxy類,所以每個代理類都會關(guān)聯(lián)一個InvocationHandler方法調(diào)用處理器搓彻。

類和所有方法都被public final修飾如绸,所以代理類只可被使用,不可以再被繼承旭贬。

每個方法都有一個Method對象來描述怔接,Method對象在static靜態(tài)代碼塊中創(chuàng)建,以“m+數(shù)字”的格式命名稀轨。

調(diào)用方法的時候通過super.h.invoke(this,m1,(Object[])null);調(diào)用扼脐。其中的super.h.invoke實際上是在創(chuàng)建代理的時候傳遞給Proxy.newProxyInstance的LogHandler對象,它繼承InvocationHandler類奋刽,負(fù)責(zé)實際的調(diào)用處理邏輯瓦侮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市佣谐,隨后出現(xiàn)的幾起案子肚吏,更是在濱河造成了極大的恐慌,老刑警劉巖狭魂,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罚攀,死亡現(xiàn)場離奇詭異,居然都是意外死亡雌澄,警方通過查閱死者的電腦和手機斋泄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掷伙,“玉大人是己,你說我怎么就攤上這事∪喂瘢” “怎么了卒废?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵沛厨,是天一觀的道長。 經(jīng)常有香客問我摔认,道長逆皮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任参袱,我火速辦了婚禮电谣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抹蚀。我一直安慰自己剿牺,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布环壤。 她就那樣靜靜地躺著晒来,像睡著了一般。 火紅的嫁衣襯著肌膚如雪郑现。 梳的紋絲不亂的頭發(fā)上湃崩,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音接箫,去河邊找鬼攒读。 笑死,一個胖子當(dāng)著我的面吹牛辛友,可吹牛的內(nèi)容都是我干的薄扁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼废累,長吁一口氣:“原來是場噩夢啊……” “哼泌辫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起九默,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宾毒,沒想到半個月后驼修,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡诈铛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年乙各,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幢竹。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耳峦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焕毫,到底是詐尸還是另有隱情蹲坷,我是刑警寧澤驶乾,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站循签,受9級特大地震影響级乐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜县匠,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一风科、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乞旦,春花似錦贼穆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亲桦,卻和暖如春崖蜜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背客峭。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工豫领, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舔琅。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓等恐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親备蚓。 傳聞我的和親對象是個殘疾皇子课蔬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353