設計模式——proxy代理模式

概述

定義

  • 代理模式顧名思義,做爲某對象的表明,去作某些事情。例如海淘、轉運公司,代收快遞等,都是生活中的代理模式。
  • 代理模式的英文叫作Proxy或Surrogate。
  • 定義:代理(Proxy)是一種設計模式,爲其餘對象提供一個代理以控制對某個對象的訪問,即經過代理對象訪問目標對象。
    • 這樣作的好處是:能夠在目標對象實現的基礎上,加強額外的功能操做,即擴展目標對象的功能.
        + 這裏使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,若是需改修改,能夠經過代理的方式來擴展該方法
  • 代理模式的關鍵點:代理對象與目標對象.代理對象是對目標對象的擴展,並會調用目標對象

角色

  • 代理模式包含以下角色:
    • Subject: 抽象角色
    • Proxy: 代理角色
    • RealSubject: 真實角色
    • UML

爲何會有代理模式?

  • 咱們在寫一個功能函數時,常常須要在其中寫入與功能不是直接相關但頗有必要的代 碼,如日誌記錄,信息發送,安全和事務支持等,這些枝節性代碼雖然是必要的,但它會帶來如下麻煩:
    • 枝節性代碼遊離在功能性代碼以外,它不是函數的目的,這是對OO是一種破壞
    • 枝節性代碼會形成功能性代碼對其它類的依賴,加深類之間的耦合,可重用性下降
    • 從法理上說,枝節性代碼應該監視着功能性代碼,而後採起行動,而不是功能性代碼 通知枝節性代碼採起行動,這比如吟遊詩人應該是主動記錄騎士的功績而不是騎士主動要求詩人記錄本身的功績

應用場景

  • 常見的代理有:
    • 遠程代理(Remote Proxy):對一個位於不一樣的地址空間對象提供一個局域表明對象,如RMI中的stub
    • 虛擬代理(Virtual Proxy):根據須要將一個資源消耗很大或者比較複雜的對象,延遲加載,在真正須要的時候才建立
    • 保護代理(Protect or Access Proxy):控制對一個對象的訪問權限。
    • 智能引用(Smart Reference Proxy):提供比目標對象額外的服務和功能。

示例

靜態代理

  • 靜態代理在使用時,須要定義接口或者父類,被代理對象與代理對象一塊兒實現相同的接口或者是繼承相同父類.
  • 關鍵:在編譯期肯定代理對象,在程序運行前代理類的.class文件就已經存在了。
  • 好比:在代理對象中實例化被代理對象或者將被代理對象傳入代理對象的構造方法

例子

  • 模擬保存動做,定義一個保存動做的接口:IUserDao.java,而後目標對象UserDao.java實現這個接口的方法,此時若是使用靜態代理方式,就須要在代理對象(UserDaoProxy.java)中也實現IUserDao接口.調用的時候經過調用代理對象的方法來調用目標對象.
  • 須要注意的是,代理對象與目標對象要實現相同的接口,而後經過調用相同的方法來調用目標對象的方法
接口:IUserDao.java

public interface IUserDao {
    void save();
}
目標對象類:UserDao.java

public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已經保存數據!----");
    }
}
代理對象:UserDaoProxy.java

public class UserDaoProxy implements IUserDao{
    //接收保存目標對象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("開始事務...");
        target.save();//執行目標對象的方法
        System.out.println("提交事務...");
    }
}
測試類:App.java

public class App {
    public static void main(String[] args) {
        //目標對象
        UserDao target = new UserDao();

        //代理對象,把目標對象傳給代理對象,創建代理關係
        UserDaoProxy proxy = new UserDaoProxy(target);

        proxy.save();//執行的是代理的方法
    }
}
  • 靜態代理總結: 能夠作到在不修改目標對象的功能前提下,對目標功能擴展.
  • 缺點:代理類和委託類實現相同的接口,同時要實現相同的方法。這樣就出現了大量的代碼重複。若是接口增長一個方法,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度。

動態代理

  • 動態代理有如下特色:
    • 在運行期,經過反射機制建立一個實現了一組給定接口的新類
    • 在運行時生成的class,必須提供一組interface給它,而後該class就宣稱它實現了這些 interface。該class的實 例能夠看成這些interface中的任何一個來用。可是這個Dynamic Proxy其實就是一個Proxy, 它不會替你做實質性的工做,在生成它的實例時你必須提供一個handler,由它接管實際的工 做。
    • 動態代理也叫作:JDK代理,接口代理
    • 接口中聲明的全部方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,咱們能夠進行靈活處理,而不須要像靜態代理那樣每個方法進行中轉。並且動態代理的應用使咱們的類職責更加單一,複用性更強

JDK中生成代理對象的API

  代理類所在包:java.lang.reflect.Proxy
  JDK實現代理只須要使用newProxyInstance方法,可是該方法須要接收三個參數,完整的寫法是:java

static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)
  注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次爲:
  • ClassLoader loader:指定當前目標對象使用類加載器,用null表示默認類加載器
  • Class [] interfaces:須要實現的接口數組
  • InvocationHandler handler:調用處理器,執行目標對象的方法時,會觸發調用處理器的方法,從而把當前執行目標對象的方法做爲參數傳入

java.lang.reflect.InvocationHandler:這是調用處理器接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,一般在該方法中實現對委託類的代理訪問。編程

// 該方法負責集中處理動態代理類上的全部方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象
// 第三個方法是調用參數。
Object invoke(Object proxy, Method method, Object[] args)

代碼示例:

接口類IUserDao.java以及接口實現類UserDao是同樣的.在這個基礎上,增長一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,而後在測試類中先創建目標對象和代理對象的聯繫,而後使用代理對象中的同名方法segmentfault

代理工廠類:ProxyFactory.java

/**
 * 建立動態代理對象
 * 動態代理不須要實現接口,可是須要指定接口類型
 */
public class ProxyFactory{
//維護一個目標對象
private Object target;
public ProxyFactory(Object target){
    this.target=target;
}
//給目標對象生成代理對象
public Object getProxyInstance(){
    return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("開始事務2");
                    //執行目標對象方法
                    Object returnValue = method.invoke(target, args);
                    System.out.println("提交事務2");
                    return returnValue;
                }
            }
    );
}
}
測試類:App.java

/**
 * 測試類
*/
public class App {
public static void main(String[] args) {
    // 目標對象
    IUserDao target = new UserDao();
    // 【原始的類型 class cn.itcast.b_dynamic.UserDao】
    System.out.println(target.getClass());

    // 給目標對象,建立代理對象
    IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
    // class $Proxy0   內存中動態生成的代理對象
    System.out.println(proxy.getClass());

    // 執行方法   【代理對象】
    proxy.save();
  }
}
  • 總結:
    • 代理對象不須要實現接口,可是目標對象必定要實現接口,不然不能用動態代理
    • 代理中的異常信息,須要特殊處理,拋出真實的異常信息
    @Override
    public Object invoke(Object instance, Method method, Object[] args) throws Throwable {
        String clazz = manager.getClass().getName();
        String methodName = method.getName();
        if (args==null)
        {
            try {
                return method.invoke(manager, args);
            } catch (InvocationTargetException e) {
                throw e.getCause();// 關鍵在於要拋出真實的異常,若是多層次代理調用,則須要循環處理
            }
        }
        ...
    }

代碼示例2

  • 使用動態代理,增長接口的權限驗證
  • PrivilegeValidationProxy代理類,代理調用時,增長權限驗證valid
  • DatasetManager,真實對象
public class PrivilegeValidationProxy<T extends MetaBean> implements InvocationHandler {

    protected MetaManager<T> manager;
    public PrivilegeValidationProxy(MetaManager<T> manager) {
        super();
        this.manager = manager;
    }

    @Override
    public Object invoke(Object instance, Method method, Object[] args) throws Throwable {
        ...
        // 驗證權限
        valid();
        method.invoke(manager, args);
        
    }
}

public class DatasetManager extends MetaManager<DatasetBean> implements DatasetManagerI {
    public static DatasetManagerI instance = newInstance();
    public static DatasetManagerI getInstance() {
        return instance;
    }
    public static DatasetManagerI newInstance() {
        DatasetManager manager = new DatasetManager();
        return (DatasetManagerI) Proxy.newProxyInstance(manager.getClass().getClassLoader(),
                new Class[] { DatasetManagerI.class }, new PrivilegeValidationProxy<DatasetBean>(manager));
    }
}

Cglib代理

  • 適用於被代理的對象未實現任何接口;
  • 上面的靜態代理和動態代理模式都是要求目標對象實現一個接口或者多個接口,可是有時候目標對象只是一個單獨的對象,並無實現任何的接口,這個時候就可使用構建目標對象子類的方式實現代理,這種方法就叫作:Cglib代理
  • Cglib代理,也叫做子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.
    • Cglib是一個強大的高性能的代碼生成包,它能夠在運行期擴展java類與實現java接口.它普遍的被許多AOP的框架使用,例如Spring + AOP和synaop,爲他們提供方法的interception(攔截)
    • Cglib包的底層是經過使用字節碼處理框架ASM來轉換字節碼並生成新的子類.
    • 代理的類不能爲final,不然報錯;目標對象的方法若是爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法.

代碼示例

目標對象類:UserDao.java

/**
 * 目標對象,沒有實現任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已經保存數據!----");
    }
}
Cglib代理工廠:ProxyFactory.java

/**
 * Cglib子類代理工廠
* 對UserDao在內存中動態構建一個子類對象
*/
public class ProxyFactory implements MethodInterceptor{
     //維護目標對象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

   //給目標對象建立一個代理對象
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
       //2.設置父類
        en.setSuperclass(target.getClass());
        //3.設置回調函數
        en.setCallback(this);
        //4.建立子類(代理對象)
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開始事務...");
        //執行目標對象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("提交事務...");
        return returnValue;
    }
}
測試類:

/**
 * 測試類
 */
public class App {

    @Test
    public void test(){
        //目標對象
        UserDao target = new UserDao();

        //代理對象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //執行代理對象的方法
        proxy.save();
    }
}

AOP(AspectOrientedProgramming):

  將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,經過對這些行爲的分離,咱們但願能夠將它們獨立到非業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼---解耦。
**在Spring的AOP編程中:設計模式

  • 若是加入容器的目標對象有實現接口,用JDK代理
  • 若是目標對象沒有實現接口,用Cglib代理**

參考

  • 大部分參考自:https://segmentfault.com/a/1190000009235245
  • http://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/proxy.html https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Proxy_pattern_diagram.svg/400px-Proxy_pattern_diagram.svg.png
相關文章
相關標籤/搜索