1. 程式人生 > >設計模式——proxy代理模式

設計模式——proxy代理模式

關系 org 類對象 設置 驗證 protect java實現 否則 總結

[toc]

概述

定義

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

角色

  • 代理模式包含如下角色:
    • Subject: 抽象角色
    • Proxy: 代理角色
    • RealSubject: 真實角色
    • 技術分享圖片

為什麽會有代理模式?

  • 我們在寫一個功能函數時,經常需要在其中寫入與功能不是直接相關但很有必要的代 碼,如日誌記錄,信息發送,安全和事務支持等,這些枝節性代碼雖然是必要的,但它會帶來以下麻煩:
    • 枝節性代碼遊離在功能性代碼之外,它不是函數的目的,這是對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方法,但是該方法需要接收三個參數,完整的寫法是:

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),將代理類寫在這個地方,然後在測試類中先建立目標對象和代理對象的聯系,然後使用代理對象中的同名方法

代理工廠類: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

設計模式——proxy代理模式