淺談代理模式及其在Java中的實(shí)現(xiàn)

代理模式是常用的結(jié)構(gòu)型設(shè)計(jì)模式之一,當(dāng)無法直接訪問某個(gè)對象或訪問某個(gè)對象存在困難時(shí)可以通過一個(gè)代理對象來間接訪問,為了保證客戶端使用的透明性,所訪問的真實(shí)對象與代理對象需要實(shí)現(xiàn)相同的接口。在Java中代理實(shí)現(xiàn)分為靜態(tài)代理和動(dòng)態(tài)代理,本文將簡要描述代理模式及其在Java中的實(shí)現(xiàn)。

代理模式

定義

給某一個(gè)對象提供一個(gè)代理或占位符,并由代理對象來控制對原對象的訪問。

結(jié)構(gòu)

代理模式UML圖

注意:上圖的注釋部分是針對Proxy類的request方法。

由圖可知,代理模式存在以下3種角色:

  • Subject:被代理類和代理類要實(shí)現(xiàn)的公共接口(request())
  • ConcreteSubject:要被代理的類,又叫委托類,它定義了代理角色所代表的真實(shí)對象
  • Proxy:代理類,代理對象扮演著中介的角色,增加被代理類的某些功能或去掉了某些服務(wù)

簡單實(shí)現(xiàn)

Subject接口:

<pre>
public interface Subject {
void request();
}
</pre>

ConcreteSubject類:
<pre>
public class ConcreteSubject implements Subject {
@Override
public void request() {
System.out.println("=======具體子類的request=======");
}
}
</pre>

Proxy類:
<pre>
public class Proxy implements Subject {
private ConcreteSubject subject;//被代理對象

public Proxy(ConcreteSubject subject){
    this.subject=subject;
}


public void preRequest(){
    System.out.println("=======代理的preRequest=======");
}

@Override
public void request() {
    preRequest();
    subject.request();//調(diào)用被代理對象的方法
    postRequest();
}

public void postRequest(){
    System.out.println("=======代理的postRequest=======");
}

}
</pre>

客戶端測試類:
<pre>
public class Test {

public static void main(String[] args) {
    //1.創(chuàng)建被代理類對象
    ConcreteSubject subject=new ConcreteSubject();
    //2.創(chuàng)建代理對象,使用構(gòu)造注入把被代理類對象注入到代理對象中
    Proxy proxy=new Proxy(subject);
    //3.調(diào)用代理對象的相應(yīng)方法
    proxy.request();
}

}
</pre>

運(yùn)行測試類,應(yīng)該看到如下結(jié)果:

以上,就實(shí)現(xiàn)了一個(gè)最簡單的代理模式。接下來看一下常見的幾種代理模式及其應(yīng)用場景。

常見代理模式及應(yīng)用場景

  1. 遠(yuǎn)程代理(Remote Proxy):為一個(gè)位于不同的地址空間的對象提供一個(gè)本地的代理對象,這
    個(gè)不同的地址空間可以是在同一臺(tái)主機(jī)中,也可是在另一臺(tái)主機(jī)中,遠(yuǎn)程代理又稱為大使
    (Ambassador)。
  2. 虛擬代理(Virtual Proxy):如果需要?jiǎng)?chuàng)建一個(gè)資源消耗較大的對象,先創(chuàng)建一個(gè)消耗相對較
    小的對象來表示,真實(shí)對象只在需要時(shí)才會(huì)被真正創(chuàng)建。
  3. 保護(hù)代理(Protect Proxy):控制對一個(gè)對象的訪問,可以給不同的用戶提供不同級(jí)別的使用權(quán)限。
  4. 緩沖代理(Cache Proxy):為某一個(gè)目標(biāo)操作的結(jié)果提供臨時(shí)的存儲(chǔ)空間,以便多個(gè)客戶端
    可以共享這些結(jié)果。
  5. 智能引用代理(Smart Reference Proxy):當(dāng)一個(gè)對象被引用時(shí),提供一些額外的操作,例如將對象被調(diào)用的次數(shù)記錄下來等。

其中,智能引用代理是使用得最廣泛的一種代理,適合的應(yīng)用如如:日志處理、權(quán)限管理、事務(wù)處理等。

代理的實(shí)現(xiàn)

代理模式的實(shí)現(xiàn)分為靜態(tài)代理和動(dòng)態(tài)代理。

靜態(tài)代理

概念

由程序員創(chuàng)建或工具生成代理類的源碼,再編譯代理類。所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類>的字節(jié)碼文件,代理類和委托類的關(guān)系在運(yùn)行前就確定了。Java編譯完成后代理類是一個(gè)實(shí)際的 class 文件。

前面舉的例子就是一個(gè)簡單的靜態(tài)代理。

靜態(tài)代理的實(shí)現(xiàn)可分為繼承和聚合,其中繼承容易導(dǎo)致類膨脹,因此推薦的實(shí)現(xiàn)方式是聚合。

靜態(tài)代理優(yōu)缺點(diǎn)

優(yōu)點(diǎn):業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性。這是代理的共有優(yōu)點(diǎn)。

缺點(diǎn):

  1. 代理對象的一個(gè)接口只服務(wù)于一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時(shí)就無法勝任了。
  2. 如果接口增加一個(gè)方法,除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。

另外,如果要按照上述的方法使用代理模式,那么真實(shí)角色(委托類)必須是事先已經(jīng)存在的,并將其作為代理對象的內(nèi)部屬性。但是實(shí)際使用時(shí),一個(gè)真實(shí)角色必須對應(yīng)一個(gè)代理角色,如果大量使用會(huì)導(dǎo)致類的急劇膨脹;此外,如果事先并不知道真實(shí)角色(委托類),該如何使用代理呢?這個(gè)問題可以通過Java的動(dòng)態(tài)代理類來解決。

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

概念

動(dòng)態(tài)代理類的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運(yùn)行時(shí)確定。

實(shí)現(xiàn)方式

Java中實(shí)現(xiàn)動(dòng)態(tài)代理主要有兩種典型方法:

  1. 使用JDK實(shí)現(xiàn)動(dòng)態(tài)代理
  2. 使用CGLIB實(shí)現(xiàn)動(dòng)態(tài)代理

我們主要來看一下第一種方法。

JDK實(shí)現(xiàn)動(dòng)態(tài)代理

JDK實(shí)現(xiàn)動(dòng)態(tài)代理
步驟
  1. 創(chuàng)建被代理的類及接口
  2. 創(chuàng)建一個(gè)接口實(shí)現(xiàn)InvocationHandler的類(調(diào)用處理器),它必須實(shí)現(xiàn)invoke方法
  3. 調(diào)用Proxy的靜態(tài)方法,動(dòng)態(tài)創(chuàng)建一個(gè)代理類的對象
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  1. 通過代理調(diào)用方法
代碼舉例

看一個(gè)最簡單的例子,大部分步驟已注釋說明。

Subject接口:
<pre>
/**

  • 被代理類和代理類要實(shí)現(xiàn)的公共接口
    */
    public interface Subject {
    void request();
    }
    </pre>

要被代理的類:
<pre>
/**

  • Subject接口實(shí)現(xiàn)
    */
    public class ConcreteSubject implements Subject{

    @Override
    public void request() {
    System.out.println("==========被代理對象的request方法==========");
    }

}
</pre>

InvocationHandler實(shí)現(xiàn)類:
<pre>
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**

  • 代理類的調(diào)用處理器
    */
    public class ProxyHandler implements InvocationHandler {
    //被代理對象
    private Subject target;

    public ProxyHandler(Subject target) {
    this.target=target;
    }

    /*

    • 參數(shù):
    • proxy:表示最終生成的代理類對象,注意不是被代理的對象
    • method:被代理對象的方法
    • args:方法的參數(shù)
    • 返回值:
    • Object方法的返回值
      */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("=========before===========");
      Object object=method.invoke(target);
      System.out.println("=========after===========");
      return object;

    }

}
</pre>
測試類:
<pre>
import java.lang.reflect.Proxy;

public class ProxyTest {

public static void main(String[] args) {
    //1.創(chuàng)建被代理對象
    ConcreteSubject subject=new ConcreteSubject();
    //2.創(chuàng)建調(diào)用處理器對象
    ProxyHandler proxyHandler=new ProxyHandler(subject);
    //3.動(dòng)態(tài)生成代理對象
    Class<?> cls=ConcreteSubject.class;//獲得被代理類的Class對象
    Subject proxySubject=(Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), proxyHandler);
    //4.通過代理對象調(diào)用方法
    proxySubject.request();
    
}

}
</pre>

運(yùn)行測試類,可以得到以下結(jié)果:


至此,用JDK實(shí)現(xiàn)了最簡單的動(dòng)態(tài)代理。
其中需要注意的地方是:

  1. 需要?jiǎng)?chuàng)建一個(gè)java.lang.reflect.InvocationHandler的實(shí)現(xiàn),作為代理類調(diào)用處理器。該接口只有一個(gè)方法:
Object invoke(Object proxy, Method method, Object[] args)
*注:參數(shù)說明見上文代碼*
  1. 調(diào)用
    java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    獲得代理實(shí)例

參數(shù)說明:

loader:定義代理類的類加載器

interfaces:代理類需要實(shí)現(xiàn)的接口列表

h:調(diào)用處理器,代理對象將會(huì)將方法調(diào)用轉(zhuǎn)發(fā)給它

返回值:

Object:一個(gè)由上述參數(shù)所指定的代理實(shí)例

動(dòng)態(tài)代理實(shí)現(xiàn)思路

實(shí)現(xiàn)功能:通過Proxy的newProxyInstance返回代理對象

  1. 聲明一段源碼(動(dòng)態(tài)代理)
  2. 編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
  3. 將這個(gè)類load到內(nèi)存當(dāng)中,產(chǎn)生一個(gè)新的對象(代理對象)
  4. return代理對象

注:可通過慕課網(wǎng):模擬JDK動(dòng)態(tài)代理實(shí)現(xiàn)思路分析及簡單實(shí)現(xiàn)了解

CGLIB

JDK提供的實(shí)現(xiàn)動(dòng)態(tài)代理的方法十分強(qiáng)大,但是也有一定的限制,主要的限制是:

只能代理實(shí)現(xiàn)接口的類,沒有實(shí)現(xiàn)接口的類不能實(shí)現(xiàn)JDK動(dòng)態(tài)代理

可以使用CGLIB來解決該問題。CGLIB的特點(diǎn)是:

  • 針對類來實(shí)現(xiàn)代理
  • 對指定目標(biāo)類生成一個(gè)子類,通過方法攔截技術(shù)攔截所有父類方法的調(diào)用
  • 由于使用繼承實(shí)現(xiàn),因此不能為final修飾的類或方法實(shí)現(xiàn)代理

動(dòng)態(tài)代理的應(yīng)用

AOP(面向切面編程),即在不改變原有類方法的基礎(chǔ)上,增加一些額外的業(yè)務(wù)邏輯。

Spring的AOP就是通過JDK動(dòng)態(tài)代理跟CGLIB動(dòng)態(tài)代理來實(shí)現(xiàn)的。

默認(rèn)的策略是如果目標(biāo)類是接口,則使用JDK動(dòng)態(tài)代理技術(shù),如果目標(biāo)對象沒有實(shí)現(xiàn)接口,則默認(rèn)會(huì)采用CGLIB代理。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容