【設計模式】代理模式之JDK動態代理與CGLIb代理區別

一、什麼是代理?

代理模式是Java中常見的一種模式,英文名字叫走Proxy或者Surrogate,代理的本意是一個人代表另一個人,或者一個機構代表另一個機構,採取行動,因而,代理和現實生活中的中介有很大的類似,你買房子、賣房子,可以自己去操作,但是需要了解和買賣房產無關的細節,如契稅等,而找一箇中介,則不用關心這些與買賣房產無直接關係的中間細節,只關心業務本身。

代理分爲靜態代理和動態代理

按照代理創建的時期,可以分爲動態代理和靜態代理:

 靜態代理:由程序員或者自動生成工具生成代理類,然後進行代理類的編譯和運行。在代理類、委託類運行之前,代理類已經以.class的格式存在。

 動態代理:在程序運行時,由反射機制動態創建而成。

1、靜態代理實例

  靜態代理UML類圖

 模式中包含的角色及其職責

Subject:抽象主題角色,抽象主題類可以是抽象類,也可以是接口,是一個最普通的業務類型定義,無特殊要求。

RealSubject:具體主題角色,也叫被委託角色、被代理角色。是業務邏輯的具體執行者。

Proxy:代理主題角色,也叫委託類、代理類。它把所有抽象主題類定義的方法給具體主題角色實現,並且在具體主題角色處理完畢前後做預處理和善後工作。(最簡單的比如打印日誌):

Subject:

/** 
 
 * 抽象主題,定義主要功能 
 
 */  
  
public interface Subject {  
  
     void operate();  
  
}  

RealSubject:

/** 
 
 * 具體主題 
 
 */  
  
public class RealSubject implements Subject{  
  
   
  
   @Override  
  
   public void operate() {  
  
        System.out.println("realsubject operatestarted......");  
  
   }  
  
}  

代理類Proxy:

/** 
 
 * 代理類 
 
 */  
  
public class Proxy implements Subject {  
  
  
   private Subject subject;  
  
  
   public Proxy(Subject subject) {  
  
        this.subject = subject;  
  
   }  
  
   
  
   @Override  
  
   public void operate() {  
  
        System.out.println("before operate......");  
  
        subject.operate();  
  
        System.out.println("after operate......");  
  
   }  
  
}  

客戶端:

/** 
 * 客戶調用 
 */  
public class Client {  
   /** 
    * @param args 
    */  
   public staticvoid main(String[] args) {  
        Subject subject = new RealSubject();  
        Proxy proxy = new Proxy(subject);  
        proxy.operate();  
   }  
}  

  從靜態代理中可以看出:

    1.接口:代理類需要實現一個接口,這個接口和委託類的接口是一樣的,這樣proxy才能和委託類行爲表現一致

    2. 方法(Method):由於接口限制,proxy類中也要有interface中的各個方法,這就造成了代碼重複

2、動態代理實例

2.1、jdk動態代理實現方式:

動態代理類克服了proxy需要繼承專一的interface接口,並且要實現相應的method的缺陷。

latex-table

java動態代理類位於java.lang.reflect包下,一般主要涉及到以下兩個類:

  1. Interface InvocationHandler:該接口中僅定義了一個方法Object:invoke(Object obj,Method method, Object[] args)。在實際使用時,第一個參數obj一般是指代理 類,method是被代理的方法,如上例中的request(),args爲該方法的參數數組。 這個抽 象方法在代理類中動態實現。
  2. Proxy:該類即爲動態代理類,作用類似於上例中的ProxySubject。
  3. Protected Proxy(InvocationHandler h):構造函數,估計用於給內部的h賦值。
  4. Static Class getProxyClass (ClassLoader loader, Class[] interfaces):獲得一個 代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部接口的數組。
  5. Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理類的一個實例,返回後的代理類可以當作被代理類使用 (可使用被代理類的在Subject接口中聲明過的方法)。

在使用動態代理類時,我們必須實現InvocationHandler, 代碼實例如下:

Subject:

package org.wavefar.core;

public interface IUser {
    void addUser();
    void delUser();
}

具體Subject:

package org.wavefar.core;

public class UserImpl implements IUser {
    @Override
    public void addUser() {
        System.out.println("添加了一個用戶!");
    }

    @Override
    public void delUser() {
        System.out.println("刪除了一個用戶!");
    }
}

代理處理器(ProxyHandler):

package org.wavefar.core;

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

public class UserProxy implements InvocationHandler {

    private  Object obj;
    public Object getInstance(Object obj){
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        System.out.println("Proxy start...");
        Object result = method.invoke(obj,args);
        System.out.println("Proxy end...");
        return result;
    }
}

測試類:

package org.wavefar.core;

public class UserProxyTest {
    public static void main(String[]args) {
        UserProxy userProxy = new UserProxy();
        IUser iUser = (IUser) userProxy.getInstance(new UserImpl());
        iUser.addUser();
        iUser.delUser();
    }
}

 對於JDK 的Proxy,有以下幾點:

    1)Interface:對於JDK proxy,業務類是需要一個Interface的,這也是一個缺陷

    2)Proxy,Proxy 類是動態產生的,這個類在調用Proxy.newProxyInstance(targetCls.getClassLoader, targetCls.getInterface,InvocationHander)之後,會產生一個Proxy類的實例。實際上這個Proxy類也是存在的,不僅僅是類的實例。這個Proxy類可以保存到硬盤上。

    3) Method:對於業務委託類的每個方法,現在Proxy類裏面都不用靜態顯示出來

    4) InvocationHandler: 這個類在業務委託類執行時,會先調用invoke方法。invoke方法再執行相應的代理操作,可以實現對業務方法的再包裝

 

2.2、CGLib 動態代理簡介

JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理,使用CGLib實現動態代理,完全不受代理類必須實現接口的限制,而且CGLib底層採用ASM字節碼生成框架,使用字節碼技術生成代理類,比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明爲final的方法進行代理,因爲CGLib原理是動態生成被代理類的子類。

下面,將通過一個實例介紹使用CGLib實現動態代理。

直接看示例:

業務類:

package org.wavefar.core;

/**
 * 這個沒實現接口
 * @author summer
 */
public class UserImpl /*implements IUser */{
    public void addUser() {
        System.out.println("添加了一個用戶!");
    }

    public void delUser() {
        System.out.println("刪除了一個用戶!");
    }
}

代理類:

package org.wavefar.core;
  
import java.lang.reflect.Method;  
  
import net.sf.cglib.proxy.Enhancer;  
import net.sf.cglib.proxy.MethodInterceptor;  
import net.sf.cglib.proxy.MethodProxy;  
  
/*
 * 使用cglib動態代理 
 * @author summer
 */
public class UserProxyCglib implements MethodInterceptor {
    private Object target;  
  
    /*
     * 創建代理對象 
     * @param target 
     * @return 
     */
    public Object getInstance(Object target) {  
        this.target = target;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.target.getClass());  
        // 回調方法  
        enhancer.setCallback(this);  
        // 創建代理對象  
        return enhancer.create();  
    }

    // 回調方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Proxy start...");
        Object object = proxy.invokeSuper(obj, args);
        System.out.println("Proxy end...");
        return object;

    }  
  
}

測試:

package org.wavefar.core; 
  
  
public class TestCglib {  
      
    public static void main(String[] args) {  
        UserProxyCglib cglib = new UserProxyCglib();  
        UserImpl userImpl = (UserImpl)cglib.getInstance(new UserImpl());  
        userImpl.addUser(); 
        userImpl.delUser();
    }  
}  

 

二、代理模式的用途,可以分爲如下幾種(From GOF):

代理模式的適用,總結爲:代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等

(1)遠程代理(Remote Proxy)  ---A remote proxy provides a local representative for an object in a different address space.爲遠程對象提供一個本地的代理對象, 典型的例子如RMI, EJB,local bean 爲remote 接口對象提供一個stub

(2)虛擬代理(Virtual Proxy) –  A virtual proxy creates expensive objects on demand.允許內存開銷較大的對象在需要的時候創建。只有我們真正需要這個對象的時候才創建。

  虛擬代理模式(Virtual Proxy)是一種節省內存的技術,它建議創建那些佔用大量內存或處理複雜的對象時,把創建這類對象推遲到使用它的時候。在特定的應用中,不同部分的功能由不同的對象組成,應用啓動的時候,不會立即使用所有的對象。在這種情況下,虛擬代理模式建議推遲對象的創建直到應用程序需要它爲止。對象被應用第一次引用時創建並且同一個實例可以被重用。這種方法優缺點並存。
  
  優點:
  
  這種方法的優點是,在應用程序啓動時,由於不需要創建和裝載所有的對象,因此加速了應用程序的啓動。
  
  缺點:
  
  因爲不能保證特定的應用程序對象被創建,在訪問這個對象的任何地方,都需要檢測確認它不是空(null)。也就是,這種檢測的時間消耗是最大的缺點。
  
  應用虛擬代理模式,需要設計一個與真實對象具有相同接口的單獨對象(指虛擬代理)。不同的客戶對象可以在創建和使用真實對象地方用相應的虛擬對象來代替。虛擬對象把真實對象的引用作爲它的實例變量維護。代理對象不要自動創建真實對象,當客戶需要真實對象的服務時,調用虛擬代理對象上的方法,並且檢測真實對象是否被創建。
  
  如果真實對象已經創建,代理把調用轉發給真實對象
  
  如果真實對象沒有被創建:
  
  1)代理對象創建真實對象
  
  2)代理對象把這個對象分配給引用變量。
  
  3)代理把調用轉發給真實對象
  
  按照這種安排,驗證對象存在和轉發方法調用這些細節對於客戶是不可見的。客戶對象就像和真實對象一樣與代理對象進行交互。因此客戶從檢測真實對象是否爲null中解脫出來,另外,由於創建代理對象在時間和處理複雜度上要少於創建真實對象。因此,在應用程序啓動的時候,用代理對象代替真實對象初始化。

(3)寫入時複製代理(Copy-On-Write Proxy) – 用來控制對象的複製,方法是延遲對象的複製,直到客戶真的需要爲止。是虛擬代理的一個變體。

(4)保護代理(Protection (Access)Proxy) –  A protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights.爲不同的客戶提供不同級別的目標對象訪問權限

(5)緩存代理(Cache Proxy) – 爲開銷大的運算結果提供暫時存儲,它允許多個客戶共享結果,以減少計算或網絡延遲。

(6)防火牆代理(Firewall Proxy) – 控制網絡資源的訪問,保護主題免於惡意客戶的侵害。

(7)同步代理(SynchronizationProxy) –在多線程的情況下爲主題提供安全的訪問。

(8)智能引用代理(Smart ReferenceProxy) -  A smart reference is a replacement for a bare pointer that performs additional actions when an object is accessed。當一個對象被引用時,提供一些額外的操作,比如將對此對象調用的次數記錄下來等。

(9)複雜隱藏代理(Complexity HidingProxy) – 用來隱藏一個類的複雜集合的複雜度,並進行訪問控制。有時候也稱爲外觀代理(Façade Proxy),這不難理解。複雜隱藏代理和外觀模式是不一樣的,因爲代理控制訪問,而外觀模式是不一樣的,因爲代理控制訪問,而外觀模式只提供另一組接口。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章