設計模式之代理模式(Proxy Pattern)

2022-09-20 18:01:01

1、代理模式

1.1、介紹

概念

代理模式(Proxy Pattern)給某一個物件提供一個代理,並由代理物件控制原物件的參照。代理物件在使用者端和目標物件之間起到中介作用 。

代理模式是常用的結構型設計模式之一,當直接存取某些物件存在問題時可以通過一個代理物件來間接存取。

用途:

  • 當提供服務方不想讓使用者存取真正角色時,採用代理模式
  • 當需要橫切一些業務時,為了不破壞原有的類,也可採用代理模式

作用:

  1. 功能增強: 在你原有的功能上,增加了額外的功能。 新增加的功能,叫做功能增強。
  2. 控制存取: 代理類不讓你存取目標,例如商家不讓使用者存取廠家。

實現代理的方式

  • 靜態代理
  • 動態代理(兩種JDK和CGLIB)

1.2 JDK動態代理

在Java的動態代理機制中,有一個介面InvocationHandler(Interface)和另一個類 Proxy(Class),二者可謂之中流砥柱。

InvocationHandler

在使用動態代理的時候,每一個代理類需要實現InvocationHandler介面,並且重寫其介面中唯一的方法invoke()。

動態代理工具類

public class DynamicProxy implements InvocationHandler {  // 實現呼叫處理介面

    //被代理的介面物件
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //建立動態代理物件用Proxy類中的newProxyInstance()方法
    // 引數資訊:
    // 	引數一:通過反射得到類載入器
    // 	引數二:需要實現的介面
    // 	引數三:處理者  這個引數需要一個InvocationHandler(介面)的物件,
    // 我們這個自定義代理類實現了InvocationHandler介面,所以用this呼叫自己
    // 返回指定介面的代理類的範例,該介面將方法呼叫分派給指定的呼叫處理程式。
    //Proxy.newProxyInstance因為與IllegalArgumentException相同的原因而Proxy.getProxyClass
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // 處理代理範例,並返回結果
    // method這個引數其實就是我們要增強的方法,也就是需要代理類去呼叫的方法,
    // 通過這個引數呼叫invoke()方法 invoke翻譯:呼叫
    // method.invoke()通過反射去呼叫我們target介面裡的方法 動態之所以就在這
    // 該方法會在動態代理過程中通過反射被執行,具體執行過程在下面解釋
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());

        return method.invoke(target, args);
    }

    private void log(String msg){
        System.out.println("我是代理類新增的訊息,使用了"+msg+"方法!!!");
    }

}

動態代理的步驟

  1. 通過實現 InvocationHandler 介面建立自己的呼叫處理器(動態代理類);

  2. 通過為 Proxy 類指定 ClassLoader 物件和一組 interface 來建立動態代理類;

  3. 通過反射機制獲得動態代理類別建構函式,其唯一引數型別是呼叫處理器介面型別;

    1. //獲取代理物件的構造方法(也就是$Proxy0(InvocationHandler h)) 
      final Constructor<?> cons = cl.getConstructor(constructorParams);
      
  4. 通過建構函式建立動態代理類範例,構造時呼叫處理器物件作為引數被傳入。

    1. //生成代理類的範例並把InvocationHandlerImpl的範例傳給它的構造方法,InvocationHandler h
      return cons.newInstance(new Object[]{h})
      

JDK動態代理步驟

JDK動態代理分為以下幾步:

  1. 拿到被代理物件的參照,並且通過反射獲取到它的所有的介面。
  2. 通過JDK Proxy類重新生成一個新的類,同時新的類要實現被代理類所實現的所有的介面。
  3. 動態生成 Java 程式碼,把新加的業務邏輯方法由一定的邏輯程式碼去呼叫。
  4. 編譯新生成的 Java 程式碼.class。
  5. 將新生成的Class檔案重新載入到 JVM 中執行。

參考:https://blog.csdn.net/jiankunking/article/details/52143504

1.3 CGLIB動態代理

JDK動態代理是通過重寫被代理物件實現的介面中的方法來實現,而CGLIB是通過繼承被代理物件來實現,和JDK動態代理需要實現指定介面一樣,CGLIB也要求代理物件必須要實現MethodInterceptor介面,並重寫其唯一的方法intercept

CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類別方法的呼叫,順勢織入橫切邏輯。(利用ASM開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理)

注意:因為CGLIB是通過繼承目標類來重寫其方法來實現的,故而如果是final和private方法則無法被重寫,也就是無法被代理。

1、JDK動態代理具體實現原理:

通過實現InvocationHandler介面建立自己的呼叫處理器;

通過為Proxy類指定ClassLoader物件和一組interface來建立動態代理;

通過反射機制獲取動態代理類別建構函式,其唯一引數型別就是呼叫處理器介面型別;

通過建構函式建立動態代理類範例,構造時呼叫處理器物件作為引數參入;

JDK動態代理是面向介面的代理模式,如果被代理目標沒有介面那麼Spring也無能為力,Spring通過Java的反射機制生產被代理介面的新的匿名實現類,重寫了其中AOP的增強方法。

2、CGLib動態代理:

利用ASM開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。

3、兩者對比:

JDK動態代理是面向介面的。

CGLib動態代理是通過位元組碼底層繼承要代理類來實現,因此如果被代理類被final關鍵字所修飾,會失敗。

4、使用注意:

如果要被代理的物件是個實現類,那麼Spring會使用JDK動態代理來完成操作(Spirng預設採用JDK動態代理實現機制);

如果要被代理的物件不是個實現類那麼,Spring會強制使用CGLib來實現動態代理。

2、AOP的實現

基於動態代理