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

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 中運行。

參考://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的實現

基於動態代理