代理模式

代理模式

在正式介紹代理模式之前,我們需要思考一下,我們?yōu)槭裁葱枰褂玫酱眍悾绻梢灾苯釉L問和使用到目標(biāo)資源對象,我們干嘛還要脫褲子放屁找一個代理類呢?

問題就出在這一點,有一些場景下,我們確實無法直接訪問到目標(biāo)資源,例如:

  • 出于安全性的考慮,目標(biāo)資源不能直接暴露給最終用戶
  • 出于易用性的考慮,目標(biāo)資源過于復(fù)雜,很多接口是用戶不會使用到的,直接使用會增大用戶使用成本
  • 出于監(jiān)控、審計等方面的考慮,目標(biāo)資源每一次的訪問都需要被監(jiān)控和記錄到
  • 只有達(dá)到某個規(guī)則或者要求才能訪問到目標(biāo)資源,而這些規(guī)則跟資源本身沒有直接關(guān)聯(lián),耦合在一起也不方便各自的維護(hù)

當(dāng)出現(xiàn)類似上述需求場景的情況下,那么你可能需要使用到代理模式了。

與裝飾器模式對比

  • 裝飾器模式側(cè)重于對被裝飾對象的功能增強
  • 代理模式側(cè)重于對被代理對象的訪問控制

靜態(tài)代理 VS 動態(tài)代理

  • 靜態(tài)代理:一個代理場景需要構(gòu)造一個代理類,造成的問題是導(dǎo)致系統(tǒng)中的代理類過多
  • 動態(tài)代理:被代理的類是代碼運行時指定的,JDK中可通過實現(xiàn) InvocationHandler 接口來實現(xiàn)動態(tài)代理

JDK 動態(tài)代理也有不足之處,它要求被代理類一定要實現(xiàn)某個接口,比如上面的 Station 類實現(xiàn)了 TicketSell 接口。如果我們的類原本是沒有實現(xiàn)接口的,總不能為了用代理而特意去給它加一個接口吧?

優(yōu)缺點比較

  1. 優(yōu)點
  • 擴(kuò)展性強,對象更智能。
  1. 缺點
  • 代理類由于做了很多額外的操作,可能使請求速度變慢。

實例

靜態(tài)代理

Calculator.java

/**
 * @Description 計算器的接口
 * @Date 2020/3/8 17:27
 **/
public interface Calculator {
    int add(int a, int b);
}

CalculatorImpl.java

/**
 * @Description 計算器接口Calculator的一個具體實現(xiàn)類
 * @Date 2020/3/8 17:50
 **/
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

CalculatorProxy.java


/**
 * @Description 計算器的代理類
 * @Date 2020/3/8 17:50
 **/
public class CalculatorProxy implements Calculator {

    private Calculator calculator;

    public CalculatorProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int a, int b) {

        // 具體執(zhí)行前可以做的工作

        int result = calculator.add(a, b);

        // 具體執(zhí)行后可以做的工作

        return result;
    }
}

單元測試

public class ProxyTest {

    @Test
    public void testStaticProxy() {
        Calculator calculator = new CalculatorImpl();
        CalculatorProxy calculatorProxy = new CalculatorProxy(calculator);
        int result = calculatorProxy.add(1, 2);
        System.out.println(result);
    }
}

在上面的代碼中,我們定義了一個接口Calculator、一個具體的實現(xiàn)類CalculatorImpl和一個代理類CalculatorProxy。在CalculatorProxy類的實現(xiàn)中,我們可以看到add方法中調(diào)用了真正的實現(xiàn)類的add方法,并且在調(diào)用的真實方法之前和之后都有機會做一些額外的工作,例如記錄日志、記錄執(zhí)行時間等。這種方式看上去非常直接,實現(xiàn)頁比較方便,不過存在一個問題,即如果需要對多個類進(jìn)行代理,并且即使代理類所需要實現(xiàn)的功能時一致的,我們依然需要為每個實現(xiàn)類都封裝一個代理類,這里會涉及到大量的冗余代碼的編寫,后面這部分冗余代碼一旦發(fā)生修改,改動量將會非常巨大。

使用動態(tài)代理將會幫助我們解決掉這個麻煩,下面我們具體看一下動態(tài)代理的實現(xiàn)方案。

動態(tài)代理

LogHandler.java

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

/**
 * @Description 日志打印處理器
 * @Author louxiujun
 * @Date 2020/3/8 18:02
 **/
public class LogHandler implements InvocationHandler {

    private Object object;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 模擬前置處理邏輯
        this.doBefore();

        Object result = method.invoke(object, args);

        // 模擬后置處理邏輯
        this.doAfter();

        return result;
    }

    private void doBefore() {
        System.out.println("do something before action");
    }

    private void doAfter() {
        System.out.println("do something after action");
    }
}

編寫動態(tài)代理類DynamicProxy.java

import com.netease.learn.designPattern.proxy.staticProxy.Calculator;

import java.lang.reflect.Proxy;

/**
 * @Description 動態(tài)代理類
 * @Author louxiujun
 * @Date 2020/3/8 18:06
 **/
public class DynamicProxy implements Calculator {

    Calculator calculator;

    public DynamicProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int a, int b) {
        LogHandler logHandler = new LogHandler(calculator);

        Calculator proxy = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(),
                                                                calculator.getClass().getInterfaces(),
                                                                logHandler);

        return proxy.add(a, b);
    }
}

單元測試:

public class ProxyTest {
/**
     * 動態(tài)代理測試
     */
    @Test
    public void testDynamicProxy() {
        Calculator calculator = new CalculatorImpl();
        DynamicProxy dynamicProxy = new DynamicProxy(calculator);
        int result = dynamicProxy.add(1, 2);
        System.out.println(result);
    }
}

輸出結(jié)果:

do something before action
do something after action
3

從上面的代碼我們可以看到動態(tài)代理的使用方式及動態(tài)代理本身的實現(xiàn)。通過Proxy.newProxyInstance來創(chuàng)建代理的方法可以為不同的委托類都創(chuàng)建代理類,在具體代理的實現(xiàn)上,所給出的是通用的實現(xiàn)方式,被代理的方法都會進(jìn)入invoke方法中,我們可以在invoke的內(nèi)部做很多的事情。從上面的代碼可以看到,使用動態(tài)代理之后,對于同樣的事情代理都只需要實現(xiàn)一次即可,就可以提供給多個不同的委托類使用了,提高了在需要使用到代理類的場景下代碼的復(fù)用性和后期的可維護(hù)性。

小結(jié)

代理在設(shè)計模式這邊叫代理模式,它的具體實現(xiàn)上通常有靜態(tài)代理和動態(tài)代理之分,本身主要借助JDK提供的API給出了動態(tài)代理實現(xiàn)的Demo,它是借助反射JDK反射的機制來實現(xiàn)的,大家在后面學(xué)習(xí)Spring AOP章節(jié)的時候,還會接觸到cdlib動態(tài)代理的實現(xiàn),它則是通過更高級的字節(jié)碼增強技術(shù)來實現(xiàn)的。

參考資料

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

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

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