面試被問到Java 靜態(tài)代理動(dòng)態(tài)代理梭依?不用怕稍算,這樣子就可以!役拴!

理解Java動(dòng)態(tài)代理需要對(duì)Java的反射機(jī)制有一定了解

什么是代理模式#

在有些情況下糊探,一個(gè)客戶不能或者不想直接訪問另一個(gè)對(duì)象,這時(shí)需要找一個(gè)中介幫忙完成某項(xiàng)任務(wù)河闰,這個(gè)中介就是代理對(duì)象科平。

例如,購(gòu)買火車票不一定要去火車站買姜性,可以通過 12306 網(wǎng)站或者去火車票代售點(diǎn)買瞪慧。又如找女朋友、找保姆部念、找工作等都可以通過找中介完成弃酌。

定義#

由于某些原因需要給某對(duì)象提供一個(gè)代理以控制對(duì)該對(duì)象的訪問。

訪問對(duì)象不適合或者不能直接引用目標(biāo)對(duì)象印机,代理對(duì)象作為訪問對(duì)象目標(biāo)對(duì)象之間的中介

代理模式的主要角色#

  • 抽象角色(Subject):通過接口或抽象類聲明真實(shí)主題和代理對(duì)象實(shí)現(xiàn)的業(yè)務(wù)方法门驾。

  • 真實(shí)角色(Real Subject):實(shí)現(xiàn)了抽象主題中的具體業(yè)務(wù)射赛,是代理對(duì)象所代表的真實(shí)對(duì)象,是最終要引用的對(duì)象奶是。

  • 代理(Proxy):提供了與真實(shí)主題相同的接口楣责,其內(nèi)部含有對(duì)真實(shí)主題的引用竣灌,它可以訪問、控制或擴(kuò)展真實(shí)主題的功能秆麸。

  • 客戶 : 使用代理角色來進(jìn)行一些操作 .

優(yōu)點(diǎn)#

  • 代理模式在客戶端與目標(biāo)對(duì)象之間起到一個(gè)中介作用和保護(hù)目標(biāo)對(duì)象的作用
  • 代理對(duì)象可以擴(kuò)展目標(biāo)對(duì)象的功能
  • 代理模式能將客戶端與目標(biāo)對(duì)象分離初嘹,在一定程度上降低了系統(tǒng)的耦合度,增加了程序的可擴(kuò)展性

缺點(diǎn)#

  • 冗余沮趣,由于代理對(duì)象要實(shí)現(xiàn)與目標(biāo)對(duì)象一致的接口屯烦,會(huì)產(chǎn)生過多的代理類。
  • 系統(tǒng)設(shè)計(jì)中類的數(shù)量增加房铭,變得難以維護(hù)驻龟。

使用動(dòng)態(tài)代理方式,可以有效避免以上的缺點(diǎn)

靜態(tài)代理#

靜態(tài)代理其實(shí)就是最基礎(chǔ)缸匪、最標(biāo)準(zhǔn)的代理模式實(shí)現(xiàn)方案翁狐。

舉例:

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Landlord . java 即真實(shí)角色

//真實(shí)角色: 房東,房東要出租房子
public class Landlord implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy . java 即代理

//代理角色:中介
public class Proxy implements Rent {

   private Landlord landlord;
   public Proxy() { }
   public Proxy(Landlord landlord) {
       this.landlord = landlord;
  }
   //租房
   public void rent(){
       seeHouse();
       landlord.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("帶房客看房");
  }
   //收中介費(fèi)
   public void fare(){
       System.out.println("收中介費(fèi)");
  }
}

Client . java 即客戶

//客戶類凌蔬,一般客戶都會(huì)去找代理露懒!
public class Client {
   public static void main(String[] args) {
       //房東要租房
       Landlord landlord = new Landlord();
       //中介幫助房東
       Proxy proxy = new Proxy(landlord);
       //客戶找中介
       proxy.rent();
  }
}

結(jié)果:

帶房客看房
房屋出租
收中介費(fèi)

Process finished with exit code 0

在這個(gè)過程中,客戶接觸的是中介砂心,看不到房東懈词,但是依舊租到了房東的房子。同時(shí)房東省了心计贰,客戶省了事钦睡。

靜態(tài)代理享受代理模式的優(yōu)點(diǎn),同時(shí)也具有代理模式的缺點(diǎn)躁倒,那就是一旦實(shí)現(xiàn)的功能增加荞怒,將會(huì)變得異常冗余和復(fù)雜,秒變光頭秧秉。

為了保護(hù)頭發(fā)褐桌,就出現(xiàn)了動(dòng)態(tài)代理模式!

動(dòng)態(tài)代理#

動(dòng)態(tài)代理的出現(xiàn)就是為了解決傳統(tǒng)靜態(tài)代理模式的中的缺點(diǎn)象迎。

具備代理模式的優(yōu)點(diǎn)的同時(shí)荧嵌,巧妙的解決了靜態(tài)代理代碼冗余,難以維護(hù)的缺點(diǎn)砾淌。

在Java中常用的有如下幾種方式:

  • JDK 原生動(dòng)態(tài)代理
  • cglib 動(dòng)態(tài)代理
  • javasist 動(dòng)態(tài)代理

JDK原生動(dòng)態(tài)代理#

上例中靜態(tài)代理類中啦撮,中介作為房東的代理,實(shí)現(xiàn)了相同的租房接口汪厨。

例子#

  1. 首先實(shí)現(xiàn)一個(gè)InvocationHandler赃春,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的invoke()方法。
  2. 然后在需要使用Rent的時(shí)候劫乱,通過JDK動(dòng)態(tài)代理獲取Rent的代理對(duì)象织中。
class RentInvocationHandler implements InvocationHandler {

    private Rent rent;

    public RentInvocationHandler(Rent rent) {
        this.rent = rent;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }

    //看房
    public void seeHouse(){
        System.out.println("帶房客看房");
    }
    //收中介費(fèi)
    public void fare(){
        System.out.println("收中介費(fèi)");
    }
    //動(dòng)態(tài)獲取代理
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this); //核心關(guān)鍵
    }
}

客戶使用動(dòng)態(tài)代理調(diào)用

public class Client {
    public static void main(String[] args) {
        Landlord landlord = new Landlord();
        //代理實(shí)例的調(diào)用處理程序
        RentInvocationHandler pih = new RentInvocationHandler(landlord);
        Rent proxy = (Rent)pih.getProxy(); //動(dòng)態(tài)生成對(duì)應(yīng)的代理類锥涕!
        proxy.rent();
    }//加入Java開發(fā)交流君樣:1025684353一起吹水聊天
}

運(yùn)行結(jié)果和前例相同

分析#

上述代碼的核心關(guān)鍵是Proxy.newProxyInstance方法,該方法會(huì)根據(jù)指定的參數(shù)動(dòng)態(tài)創(chuàng)建代理對(duì)象狭吼。

它三個(gè)參數(shù)的意義如下:

  1. loader层坠,指定代理對(duì)象的類加載器
  2. interfaces,代理對(duì)象需要實(shí)現(xiàn)的接口刁笙,可以同時(shí)指定多個(gè)接口
  3. handler破花,方法調(diào)用的實(shí)際處理者,代理對(duì)象的方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到這里

Proxy.newProxyInstance會(huì)返回一個(gè)實(shí)現(xiàn)了指定接口的代理對(duì)象采盒,對(duì)該對(duì)象的所有方法調(diào)用都會(huì)轉(zhuǎn)發(fā)給InvocationHandler.invoke()方法旧乞。

因此,在invoke()方法里我們可以加入任何邏輯磅氨,比如修改方法參數(shù)尺栖,加入日志功能、安全檢查功能等等等等……

小結(jié)#

顯而易見烦租,對(duì)于靜態(tài)代理而言延赌,我們需要手動(dòng)編寫代碼代理實(shí)現(xiàn)抽象角色的接口。

而在動(dòng)態(tài)代理中叉橱,我們可以讓程序在運(yùn)行的時(shí)候自動(dòng)在內(nèi)存中創(chuàng)建一個(gè)實(shí)現(xiàn)抽象角色接口的代理挫以,而不需要去單獨(dú)定義這個(gè)類,代理對(duì)象是在程序運(yùn)行時(shí)產(chǎn)生的窃祝,而不是編譯期掐松。

對(duì)于從Object中繼承的方法,JDK Proxy會(huì)把hashCode()粪小、equals()大磺、toString()這三個(gè)非接口方法轉(zhuǎn)發(fā)給InvocationHandler,其余的Object方法則不會(huì)轉(zhuǎn)發(fā)探膊。

CGLIB動(dòng)態(tài)代理#

JDK動(dòng)態(tài)代理是基于接口的杠愧,如果對(duì)象沒有實(shí)現(xiàn)接口該如何代理呢?CGLIB代理登場(chǎng)

CGLIB(Code Generation Library)是一個(gè)基于ASM的字節(jié)碼生成庫逞壁,它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成流济。CGLIB通過繼承方式實(shí)現(xiàn)代理。

使用cglib需要引入cglib的jar包腌闯,如果你已經(jīng)有spring-core的jar包绳瘟,則無需引入,因?yàn)閟pring中包含了cglib姿骏。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
//加入Java開發(fā)交流君樣:1025684353一起吹水聊天

例子#

來看示例糖声,假設(shè)我們有一個(gè)沒有實(shí)現(xiàn)任何接口的類Landlord

public class Landlord{
    public void rent() {
        System.out.println("房屋出租");
    }
}

因?yàn)闆]有實(shí)現(xiàn)接口,所以使用通過CGLIB代理實(shí)現(xiàn)如下:

首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的intercept()方法
c

public class RentMethodInterceptor implements MethodInterceptor {
    private Object target;//維護(hù)一個(gè)目標(biāo)對(duì)象
    public RentMethodInterceptor(Object target) {
        this.target = target;
    }
    //為目標(biāo)對(duì)象生成代理對(duì)象
    public Object getProxyInstance() {
        //工具類
        Enhancer en = new Enhancer();
        //設(shè)置父類
        en.setSuperclass(target.getClass());
        //設(shè)置回調(diào)函數(shù)
        en.setCallback(this);
        //創(chuàng)建子類對(duì)象代理
        return en.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("看房");
        // 執(zhí)行目標(biāo)對(duì)象的方法
        Object returnValue = method.invoke(target, objects);
        System.out.println("中介費(fèi)");
        return null;
    }
}

客戶通過CGLIB動(dòng)態(tài)代理獲取代理對(duì)象

public class Client {
    public static void main(String[] args) {
        Landlord target = new Landlord();
        System.out.println(target.getClass());
        //代理對(duì)象
        Landlord proxy = (Landlord) new RentMethodInterceptor(target).getProxyInstance();
        System.out.println(proxy.getClass());
        //執(zhí)行代理對(duì)象方法
        proxy.rent();
    }//加入Java開發(fā)交流君樣:1025684353一起吹水聊天
}

運(yùn)行輸出結(jié)果和前例相同

分析#

對(duì)于從Object中繼承的方法姨丈,CGLIB代理也會(huì)進(jìn)行代理,如hashCode()擅腰、equals()蟋恬、toString()等,但是getClass()趁冈、wait()等方法不會(huì)歼争,因?yàn)樗莊inal方法,CGLIB無法代理渗勘。

其實(shí)CGLIB和JDK代理的思路大致相同

上述代碼中沐绒,通過CGLIB的Enhancer來指定要代理的目標(biāo)對(duì)象、實(shí)際處理代理邏輯的對(duì)象旺坠。

最終通過調(diào)用create()方法得到代理對(duì)象乔遮,對(duì)這個(gè)對(duì)象所有非final方法的調(diào)用都會(huì)轉(zhuǎn)發(fā)給MethodInterceptor.intercept()方法

intercept()方法里我們可以加入任何邏輯取刃,同JDK代理中的invoke()方法

通過調(diào)用MethodProxy.invokeSuper()方法蹋肮,我們將調(diào)用轉(zhuǎn)發(fā)給原始對(duì)象,具體到本例璧疗,就是Landlord的具體方法坯辩。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似不恭,都是方法調(diào)用的中轉(zhuǎn)站承桥。

final類型#

CGLIB是通過繼承的方式來實(shí)現(xiàn)動(dòng)態(tài)代理的,有繼承就不得不考慮final的問題农猬。我們知道final類型不能有子類却音,所以CGLIB不能代理final類型改抡,遇到這種情況會(huì)拋出類似如下異常:

java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete

同樣的,final方法是不能重載的僧家,所以也不能通過CGLIB代理雀摘,遇到這種情況不會(huì)拋異常,而是會(huì)跳過final方法只代理其他方法八拱。

其他方案#

  • 使用ASM在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類
  • 使用javassist在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類

javassist也是常用的一種動(dòng)態(tài)代理方案阵赠,ASM速度非常快肌稻,這里不在進(jìn)行展開清蚀。

尾聲#

動(dòng)態(tài)代理是[Spring AOP(https://jq.qq.com/?_wv=1027&k=0IsBuUb0)(Aspect Orient Programming, 面向切面編程)的實(shí)現(xiàn)方式,了解動(dòng)態(tài)代理原理爹谭,對(duì)理解Spring AOP大有幫助枷邪。

  • 如spring等這樣的框架,要增強(qiáng)具體業(yè)務(wù)的邏輯方法诺凡,不可能在框架里面去寫一個(gè)靜態(tài)代理類东揣,太蠢了践惑,只能按照用戶的注解或者xml配置來動(dòng)態(tài)生成代理類。
  • 業(yè)務(wù)代碼內(nèi)嘶卧,當(dāng)需要增強(qiáng)的業(yè)務(wù)邏輯非常通用(如:添加log尔觉,重試,統(tǒng)一權(quán)限判斷等)時(shí)芥吟,使用動(dòng)態(tài)代理將會(huì)非常簡(jiǎn)單侦铜,如果每個(gè)方法增強(qiáng)邏輯不同,那么靜態(tài)代理更加適合钟鸵。
  • 使用靜態(tài)代理時(shí)钉稍,如果代理類和被代理類同時(shí)實(shí)現(xiàn)了一個(gè)接口,當(dāng)接口方法有變動(dòng)時(shí)棺耍,代理類也必須同時(shí)修改贡未,代碼將變得臃腫且難以維護(hù)。

最后蒙袍,祝大家早日學(xué)有所成羞秤,拿到滿意offer,快速升職加薪左敌,走上人生巔峰瘾蛋。

可以的話請(qǐng)給我一個(gè)三連支持一下我喲??????【獲取資料】

[圖片上傳失敗...(image-30a036-1626336314809)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市矫限,隨后出現(xiàn)的幾起案子哺哼,更是在濱河造成了極大的恐慌,老刑警劉巖叼风,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件取董,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡无宿,警方通過查閱死者的電腦和手機(jī)茵汰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孽鸡,“玉大人蹂午,你說我怎么就攤上這事”蚣睿” “怎么了豆胸?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)巷疼。 經(jīng)常有香客問我晚胡,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任估盘,我火速辦了婚禮瓷患,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遣妥。我一直安慰自己尉尾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布燥透。 她就那樣靜靜地躺著,像睡著了一般辨图。 火紅的嫁衣襯著肌膚如雪班套。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天故河,我揣著相機(jī)與錄音吱韭,去河邊找鬼。 笑死鱼的,一個(gè)胖子當(dāng)著我的面吹牛理盆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凑阶,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼猿规,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了宙橱?” 一聲冷哼從身側(cè)響起姨俩,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎师郑,沒想到半個(gè)月后环葵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宝冕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年张遭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片地梨。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菊卷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宝剖,到底是詐尸還是另有隱情的烁,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布诈闺,位于F島的核電站渴庆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜襟雷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一刃滓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耸弄,春花似錦咧虎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捌显,卻和暖如春茁彭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扶歪。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工理肺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人善镰。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓妹萨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親炫欺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乎完,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容