設計模式之代理模式(Proxy)詳解及代碼示例


一、代理模式的定義

  代理模式的定義:由於某些原因需要給某對象提供一個代理以控制對該對象的訪問。這時,訪問對象不適合或者不能直接引用目標對象,代理對象作為訪問對象和目標對象之間的中介,代理模式也叫做委托模式。

二、為什么使用代理模式

  • 中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用,其特征是代理類和委托類實現相同的接口
  • 開閉原則,增加功能:代理類除了是客戶類和委托類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委托類的功能,這樣做我們只需要修改代理類而不需要再修改委托類,符合代碼設計的開閉原則。代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后對返回結果的處理等。代理類本身並不真正實現服務,而是同過調用委托類的相關方法,來提供特定的服務。真正的業務功能還是由委托類來實現,但是可以在業務功能執行的前后加入一些公共的服務。例如我們想給項目加入緩存、日志這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委托類。

三、代理模式優缺點

  代理模式的主要優點有:

  • 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;
  • 代理對象可以擴展目標對象的功能;
  • 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度;

  代理模式的主要缺點是:

  • 在客戶端和目標對象之間增加一個代理對象,會造成請求處理速度變慢;
  • 增加了系統的復雜度;

四、代理模式的結構與實現

  代理的實現是有多種方式的,常見就是靜態代理、動態代理(JDK動態代理、CGLIB動態代理),因此接下來一一講解這三種實現方式。

  1、靜態代理

  靜態代理模式的結構比較簡單,主要是通過定義一個繼承抽象主題的代理來包含真實主題,從而實現對真實主題的訪問。代理模式的主要角色如下:

  • 抽象主題(Subject)類:通過接口或抽象類聲明真實主題和代理對象實現的業務方法。
  • 真實主題(Real Subject)類:實現了抽象主題中的具體業務,是代理對象所代表的真實對象,是最終要引用的對象。
  • 代理(Proxy)類:提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。

  其結構圖如圖所示。

            

  代碼如下:

public class ProxyTest
{
    public static void main(String[] args)
    {
        Proxy proxy=new Proxy();
        proxy.Request();
    }
}
//抽象主題
interface Subject
{
    void Request();
}
//真實主題
class RealSubject implements Subject
{
    public void Request()
    {
        System.out.println("訪問真實主題方法...");
    }
}
//代理
class Proxy implements Subject
{
    private RealSubject realSubject;
    public void Request()
    {
        if (realSubject==null)
        {
            realSubject=new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }
    public void preRequest()
    {
        System.out.println("訪問真實主題之前的預處理。");
    }
    public void postRequest()
    {
        System.out.println("訪問真實主題之后的后續處理。");
    }
}

  輸出結果為:

訪問真實主題之前的預處理。
訪問真實主題方法...
訪問真實主題之后的后續處理。

  靜態代理優缺點

  • 優點:可以做到在不修改目標對象的功能前提下,對目標功能擴展。
  • 缺點:因為代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護。

  2、動態代理(JDK動態代理)

  靜態代理會手動創建很多代理類的問題,動態代理就解決了這個問題,其中一種就是JDK自帶動態代理。其通過自己實現InvocationHandler來實現動態代理,真正的代理對象由JDK再運行時為我們動態的來創建。其結構圖如下:

        

  本例展示一個車啟動的例子,其代碼實現如下:

//目標類接口
interface Car{
    void run();
}

//目標類
class Benz implements Car{

    @Override
    public void run() {
        System.out.println("奔馳車跑起來了");
    }
}

class CarUtils{
    public static void methodBefore() {
        System.out.println("跑之前要點火 ... ...");
    }
    
    public static void methodAfter() {
        System.out.println("跑起來后會換擋 ... ...");
    }
}

class MyInvocationHandle implements InvocationHandler{
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            CarUtils.methodBefore();
            method.invoke(target, args);
            CarUtils.methodAfter();
            return null;
    }
}

//生產代理對象的工廠
class MyProxyFactory{
    public static Object getProxy(Object target) {
        MyInvocationHandle handle = new MyInvocationHandle();
        handle.setTarget(target);
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handle);
        return proxy;
    }
 }

public class ProxyTest {
    public static void main(String[] args) {
      Car car = new Benz();
      Car proxy =(Car) MyProxyFactory.getProxy(car);
      proxy.run();
    }
}

   注意Proxy.newProxyInstance()方法接受三個參數:

  • ClassLoader loader: 指定當前目標對象使用的類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces: 指定目標對象實現的接口的類型,使用泛型方式確認類型
  • InvocationHandler指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法

  JDK動態代理總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。但是JDK自帶動態代理只能支持實現了Interface的類。

  3、動態代理(CGLIB動態代理)

  JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLIB了。CGLIB采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,並在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因為采用的是繼承,所以不能對final修飾的類進行代理。

  我們通過實現MethodInterceptor來實現CGLIB動態代理,CGLIB動態代理類圖關系如下圖所示:

              

   代碼如下:

class ArraySort{
    public void quickSort(int[] arr) {
        Arrays.sort(arr);
    }

    public void selectSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = i+1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    int temp = 0;
                    temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

    public void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = 0;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

class CglibInteceptor implements MethodInterceptor {

    private Object object;

    public CglibInteceptor(Object object) {
        this.object = object;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("CGLIB動態代理執行前");
        Object result = method.invoke(object,objects);
        System.out.println("CGLIB動態代理執行后");

        return result;
    }

    public Object getProxy(){ Enhancer enhancer = new Enhancer(); enhancer.setCallback(this); enhancer.setSuperclass(object.getClass()); return enhancer.create(); }
}
public class CGLibProxyTest { public static void main(String[] args) { int[] arr = new int[100000]; for (int i = 0; i < arr.length; i++) { arr[i] = (int) (Math.random() * 1000); } ArraySort arraySort = new ArraySort(); arraySort = (ArraySort) new CglibInteceptor(arraySort).getProxy(); arraySort.bubbleSort(arr); arraySort.selectSort(arr); arraySort.quickSort(arr); } }

  CGLIB代理總結: CGLIB創建的動態代理對象比JDK創建的動態代理對象的性能更高,但是CGLIB創建代理對象時所花費的時間卻比JDK多得多。所以對於單例的對象,因為無需頻繁創建對象,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是采用動態創建子類的方法,對於final修飾的方法無法進行代理。

  以上就是三種代理具體實現。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。在Spring的AOP編程中: 如果加入容器的目標對象有實現接口,用JDK代理;如果目標對象沒有實現接口,用Cglib代理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM