1. 程式人生 > >設計模式與應用:代理模式(三種詳解)

設計模式與應用:代理模式(三種詳解)

簡介

Proxy代理模式,是構造型的設計模式之一

代理模式為其他物件提供代理以控制這個物件的訪問。

所謂代理,是指具有與代理元(被代理物件)具有相同介面的類。client需要通過代理與被代理的目標類互動,代理類就是在互動的過程中(前後)進行特殊處理。

注:這裡討論的代理都是對介面方法的代理。目前實現代理的方式:靜態代理和jdk動態代理都是對介面方法的增強。而對於沒有介面的類的方法,增強就需要使用CGLIB的實現方式。

應用場景

  • 對於不改變原方法程式碼情況

成熟框架正在使用:

  • springAOP核心實現就是動態代理
  • hibernate等一些ORM框架大量使用
  • 一些web層框架也有使用

結構圖

代理模式的核心是對目標方法/類進行功能增強!無論哪種代理方式他的核心思想來源都是於此結構
代理模式結構圖

角色和職責

靜態代理

  • subject(抽象主題角色):真實主題與代理主題的共同介面
  • RealSubject(真實主題角色):定義代理角色所代表的真實物件
  • Proxy(代理主題角色):含有對真實主題角色的引用,代理角色通常在將客戶端呼叫傳遞給真實主題物件之前或之後執行某些操作,而不是單純返回真實物件。

靜態代理實現

靜態代理
subject介面

package com.mym.designmodel.proxy.staticProxy;

/**
 * 職責: Subject 真實物件與代理物件的共同介面
 */
public interface Subject { public void eat(); }

真實實現類

package com.mym.designmodel.proxy.staticProxy;

/**
 * 職責:RealSubject 真實物件的代理類
 */
public class RealSubject implements Subject{
    @Override
    public void eat() {
        System.out.println("吃飯!");
    }
}

代理實現類

package com.mym.designmodel.proxy.staticProxy;

/**
 * 職責 ProxySubject 代理物件的實現類
 */
public class ProxySubject implements Subject{ RealSubject realSubject = null; @Override public void eat() { if(realSubject == null){ realSubject = new RealSubject(); } prepare(); realSubject.eat(); clean(); } private void prepare(){ System.out.println("準備碗筷!"); } private void clean(){ System.out.println("清洗碗筷!"); } }

測試

package com.mym.designmodel.proxy.staticProxy;

/**
 * 測試靜態代理
 */
public class MainClass {
    public static void main(String[] args) {
        ProxySubject proxySubject = new ProxySubject();
        proxySubject.eat();
    }
}

結果:

準備碗筷!
吃飯!
清洗碗筷!

JDK動態代理

jdk 有動態代理支援.參看jdk文件中的Proxy類
jdk動態代理的原理我先一句話概括下:通過類裝載器拿到真實實現類真實實現類的介面位元組碼檔案,然後構造生成一個代理類(是有真實類位元組碼檔案的)。生成完後此時又迴歸待靜態代理的uml結構上了。
jdk動態代理深層原始碼剖析可以參考:http://rejoy.iteye.com/blog/1627405

  • InvocationHandler 介面
  • invoke方法
  • Proxy.newProxyIntance()

jdk動態代理實現

jdk

代理介面

package com.mym.designmodel.proxy.dynamicProxy;

/**
 * 職責: Subject 真實物件與代理物件的共同介面
 */
public interface Subject {

    public void eat();
}

真實實現類

package com.mym.designmodel.proxy.dynamicProxy;

/**
 * 職責:RealSubject 真實物件的代理類
 */
public class RealSubject implements Subject {
    @Override
    public void eat() {
        System.out.println("吃飯!");
    }
}

動態代理 方法處理類

package com.mym.designmodel.proxy.dynamicProxy;

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

/**
 * proxy 處理
 */
public class ProxyHandler implements InvocationHandler{

    private RealSubject realSubject = null;

    public void setRealSubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        prepare();
        Object result = method.invoke(realSubject, args);
        clean();
        return result;
    }

    private void prepare(){
        System.out.println("dynamic 準備碗筷!");
    }

    private void clean(){
        System.out.println("dynamic 清洗碗筷!");
    }
}

測試

package com.mym.designmodel.proxy.dynamicProxy;

import java.lang.reflect.Proxy;

/**
 * 測試動態代理
 */
public class MainClass {

    public static void main(String[] args) {
        ProxyHandler proxyHandler = new ProxyHandler();
        proxyHandler.setRealSubject(new RealSubject());
        Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), proxyHandler);
        subject.eat();
    }
}

結果

dynamic 準備碗筷!
吃飯!
dynamic 清洗碗筷!

CGLIB動態代理

cglib是一個框架,需要額外匯入包。使用的是與jdk動態代理不同的技術實現,即asm的位元組碼技術。

cglib動態代理也是位元組碼層面的代理實現方式。原理概括一下:
CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。

cglib動態代理實現

若使用maven等版本控制來管理依賴則去maven倉庫搜尋cglib引入依賴即可。
若沒有依賴管理則需要把cglib、asm的jar包引入到工程classpath下才可以進行測試使用。jar包同樣可以上maven倉庫搜尋然後下載jar並加入到工程path中就可以。
筆者測試使用的版本是:
- cglib-3.2.4.jar
- asm-5.1.jar

cglib

被代理類

package com.mym.designmodel.proxy.cglib;

/**
 * 職責:RealSubject 真實物件
 */
public class RealSubject{
    public void eat() {
        System.out.println("吃飯!");
    }
}

代理實現

package com.mym.designmodel.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * cglib實現動態代理
 */
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    /*
    * getProxy(SuperClass.class)方法通過入參即父類的位元組碼,通過擴充套件父類的class來建立代理物件。
    * intercept()方法攔截所有目標類方法的呼叫,
    *   obj表示目標類的例項,
    *   method為目標類方法的反射物件,
    *   args為方法的動態入參,
    *   proxy為代理類例項。
    * proxy.invokeSuper(obj, args)通過代理類呼叫父類中的方法
    * */
    public Object getProxy(Class clazz){
        //設定需要建立子類的類
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通過位元組碼技術動態建立子類例項
        return enhancer.create();
    }

    //實現MethodInterceptor介面方法
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        prepare();
        Object result = proxy.invokeSuper(obj, args);//通過代理類呼叫父類中的方法
        clean();
        return result;
    }

    private void prepare(){
        System.out.println("CGLIB 準備碗筷!");
    }

    private void clean(){
        System.out.println("CGLIB 清洗碗筷!");
    }
}

測試:

package com.mym.designmodel.proxy.cglib;

/**
 * 測試CGLIB代理
 */
public class MainClass {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        RealSubject proxy = (RealSubject) cglibProxy.getProxy(RealSubject.class);
        proxy.eat();
    }
}

執行:

CGLIB 準備碗筷!
吃飯!
CGLIB 清洗碗筷!

靜態代理、jdk動態代理、cglib動態代理對比

靜態代理

缺點:

  • 冗餘:由於代理物件要實現與目標物件一致的介面,會產生過多的代理類。
  • 不易維護:一旦介面增加方法,目標物件與代理物件都要進行修改。

jdk動態代理

  • 不需要實現介面,但是要求目標物件必須實現介面,否則不能使用動態代理

CGLIB動態代理

  • 可以代理沒有實現介面的類。
  • CGLIB是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件Java類與實現Java介面。它廣泛的被許多AOP的框架使用, 例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
  • CGLIB包的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。不鼓勵直接使用ASM,因為它需要你對JVM內部結構包括class檔案的格式和指令集都很熟悉

靜態代理、jdk動態代理、cglib動態代理對比

  • 靜態代理在編譯時就已經實現,編譯完成後代理類是一個實際的class檔案
  • 靜態代理實現簡單,但只能為一個目標物件服務。如果目標物件過多,則會產生很多代理類

  • JDK動態代理需要目標物件實現業務介面,代理類只需實現InvocationHandler介面

  • JDK動態代理是在執行時動態生成的,即編譯完成後沒有實際的class檔案,而是在執行時動態生成類位元組碼,並載入到JVM中
  • 而JDK動態代理須實現InvocationHandler介面,通過反射代理方法,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。

  • cglib代理無需實現介面,通過生成類位元組碼實現代理,比反射稍快,不存在效能問題,但cglib會繼承目標物件,需要重寫方法,所以目標物件不能為final類

  • cglib代理的物件無需實現介面,達到代理類無侵入

代理模式與裝飾模式對比

代理模式和裝飾模式都是對 目標 方法/類 進行功能增強。
詳細對比請參看筆者對裝飾模式的介紹,這裡就不贅述
設計模式與應用:裝飾模式