1. 程式人生 > >設計模式:代理模式(Proxy)

設計模式:代理模式(Proxy)

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

這裡寫圖片描述
代理模式中的角色:

  1. 抽象主題角色(Subject):聲明瞭目標物件和代理物件的共同介面,這樣一來在任何可以使用目標物件的地方都可以使用代理物件。
  2. 具體主題角色(RealSubject):也稱為委託角色或者被代理角色。定義了代理物件所代表的目標物件。
  3. 代理主題角色(Proxy):也叫委託類、代理類。代理物件內部含有目標物件的引用,從而可以在任何時候操作目標物件;代理物件提供一個與目標物件相同的介面,以便可以在任何時候替代目標物件。代理物件通常在客戶端呼叫傳遞給目標物件之前或之後,執行某個操作,而不是單純地將呼叫傳遞給目標物件。
     代理模式又分為靜態代理
    動態代理。靜態代理是由程式猿建立或特定工具自動生成原始碼,再對其編譯。在程式執行前,代理類的.class檔案就已經存在了。動態代理是在程式執行時,通過運用反射機制動態的建立而成。

靜態代理

案例:
1 Subject

public interface Subject
{
    void operate();
}

2 RealSubject

public class RealSubject implements Subject
{
    @Override
    public void operate()
    {
        System.out.println("RealSubject"
); } }

3 Proxy

public class Proxy implements Subject
{
    private Subject subject = null;

    @Override
    public void operate()
    {
        if(subject == null)
            subject = new RealSubject();
        System.out.print("I'm Proxy, I'm invoking...");
        this.subject.operate();
    }
}

4 測試程式碼

        Subject subject = new Proxy();
        subject.operate();

輸出:I’m Proxy, I’m invoking…RealSubject
 從上面例子可以看出代理物件將客戶端的呼叫為派給目標物件,在呼叫目標物件的方法之前和之後都可以執行特定的操作。

動態代理

 動態代理是指在執行時,動態生成代理類。即,代理類的位元組碼將在執行時生成並載入當前的ClassLoader。與靜態代理類想比,動態類有諸多好處。首先,不需要為真是主題寫一個形式上完全一樣的封裝類,假如主題介面中的方法很多,為每一個介面寫一個代理方法也是非常煩人的事,如果介面有變動,則真實主題和代理類都要修改,不利於系統維護;其次,使用一些動態代理的生成方法甚至可以在執行時指定代理類的執行邏輯,從而大大提升系統的靈活性。

Jdk動態代理

 Jdk的動態代理是基於介面的。現在想要為RealSubject這個類建立一個動態代理物件,Jdk主要會做一下工作:

  1. 獲取RealSubject上的所有介面列表
  2. 確定要生成的代理類的類名,預設為:com.sun.proxy.$ProxyXXXX;
  3. 根據需要實現的介面資訊,在程式碼中動態建立該Proxy類的位元組碼;
  4. 將對應的位元組碼轉換為對於的class物件;
  5. 建立InvocationHandler例項handler,用來處理Proxy所有方法的呼叫;
  6. Proxy的class物件以建立的handler物件為引數,例項化一個proxy物件;

 Jdk通過java.lang.reflect.Proxy包來支援動態代理,在Java中要建立一個代理物件,必須呼叫Proxy類的靜態方法newProxyInstance,該方法的原型如下:
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
 其中:
loader,表示類載入器,對於不同來源(系統庫或網路等)的類需要不同的類載入器來載入,這是Java安全模型的一部分。可以使用null來使用預設的載入器;
interfaces,表示介面或物件的陣列,它就是前述代理物件和真實物件都必須共有的父類或者介面;
handler,表示呼叫處理器,它必須是實現了InvocationHandler介面的物件,其作用是定義代理物件中需要執行的具體操作。
 InvocationHandler之於Proxy,就如Runnable之於Thread。InvocationHandler介面中只有一個方法invoke,它的作用就跟Runnable中的run方法類似,定義了代理物件在執行真實物件的方法時所希望執行的動作。其原型如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 其中:
proxy,表示執行這個方法的代理物件;
method,表示真實物件實際需要執行的方法(關於Method類參見Java的反射機制);
args,表示真實物件實際執行方法時所需的引數。
 在實際的程式設計中,需要優先定義一個實現InvocationHandler介面的呼叫處理器物件,然後將它作為建立代理類例項的引數。(抑或在呼叫newProxyInstance方法時使用匿名內部類。)這樣就得到了代理物件。
 真實物件本身的例項化在呼叫處理器物件內部完成,例項化時需要的引數也應該及時傳入呼叫處理器物件中。這樣一來就完成了代理物件對真實物件的包裝,而代理物件需要執行的額外操作也在invoke方法中處理。
 其後,在客戶端中,如果需要使用真實物件時,就可以用代理物件來替代它了(有時需要型別強制轉化)。

案例:(修改代理類Proxy)

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

public class ProxyHandler implements InvocationHandler
{
    Object obj = null;

    public Object newProxyInstance(Object realObj){
        this.obj = realObj;
        Class<?> classType = this.obj.getClass();
        return Proxy.newProxyInstance(classType.getClassLoader(), classType.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        System.out.print("I'm Proxy, I'm invoking...");
        method.invoke(obj, args);
        System.out.println("invoke end!");
        return null;
    }
}

測試程式碼:

        Subject subject = (Subject) new ProxyHandler().newProxyInstance(new RealSubject());
        subject.operate();

輸出結果:

I'm Proxy, I'm invoking...RealSubject
invoke end!

 動態代理模式通過使用反射,可以在執行期決定載入哪個類,避免了一個類對應一個代理的問題;同時,通過統一的invoke方法,統一了代理類對原函式的處理過程,使用動態代理很大程度上減少了重複的程式碼,降低了維護的複雜性和成本。
稍微修改一下程式碼:
1 Subject

public interface Subject
{
    String operate1();
    String operate2();
    String operate3();
    String operate4();
    String operate5();
}

2 RealSubject

public class RealSubject1 implements Subject
{
    @Override
    public String operate1()
    {
        return "RealSubject-operate1()";
    }

    @Override
    public String operate2()
    {
        return "RealSubject-operate2()";
    }

    @Override
    public String operate3()
    {
        return "RealSubject-operate3()";
    }

    @Override
    public String operate4()
    {
        return "RealSubject-operate4()";
    }

    @Override
    public String operate5()
    {
        return "RealSubject-operate5()";
    }
}

public class RealSubject2 implements Subject
{
    @Override
    public String operate1()
    {
        return "RealSubject2-operate1()";
    }

    @Override
    public String operate2()
    {
        return "RealSubject2-operate2()";
    }

    @Override
    public String operate3()
    {
        return "RealSubject2-operate3()";
    }

    @Override
    public String operate4()
    {
        return "RealSubject2-operate4()";
    }

    @Override
    public String operate5()
    {
        return "RealSubject2-operate5()";
    }
}

3 Proxy

public class ProxyHandler implements InvocationHandler
{
    Object obj = null;

    public Object newProxyInstance(Object realObj){
        this.obj = realObj;
        Class<?> classType = this.obj.getClass();
        return Proxy.newProxyInstance(classType.getClassLoader(), classType.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        System.out.print("I'm Proxy, I'm invoking...");
        Object object = method.invoke(obj, args);
        System.out.println(object);
        return object;
    }
}

4 測試程式碼

        Subject object = new RealSubject();
        Subject subject = (Subject) new ProxyHandler().newProxyInstance(new RealSubject());
        Subject subject2 = (Subject) new ProxyHandler().newProxyInstance(new RealSubject2());
        subject.operate2();
        subject2.operate4();

輸出:

I'm Proxy, I'm invoking...RealSubject-operate2()
I'm Proxy, I'm invoking...RealSubject2-operate4()

 這下可以明顯的看到動態代理相比於靜態代理的優勢了吧。
當在程式碼階段規定了靜態代理關係,Proxy類通過編譯器編譯成class檔案,當系統執行時,此class已經存在了。這種靜態的代理模式固然在訪問無法訪問的資源,增強現有介面的業務功能方面有很大的優點,但是大量使用這種靜態代理,會是我們系統內的類的規模增大,並且不易維護;並且由於Proxy和RealSubject的功能本質上是相同的,Proxy只是起到了中介的左右,這種代理在系統中的存在,導致系統結構比較臃腫和鬆散

CGLIB動態代理

 生成動態代理的方法很多,不止jdk自帶的動態代理這一種,還有CGLIB,Javassist或者ASM。Jdk的動態代理依靠介面實現,如果有些類並沒有實現介面,則不能使用jdk代理,這就要用到CGLIB代理了。CGLIB是針對類來實現的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理。
 CGLIB建立某個類A的動態代理類的模式是:

  1. 查詢A上的所有非final的public型別的方法定義
  2. 將這些方法的定義轉換成位元組碼
  3. 將組成的位元組碼轉換成相應的代理的class物件
  4. 實現MethodInterceptor介面,用來處理對代理類上所有方法的請求(這個介面和Jdk動態代理InvocationHandler的功能和角色是一樣的)

修改上面的案例(需要用到cglib-nodep-2.2.jar和asm.jar兩個jar包,可以在這裡下載,免費的哦):
1 真實代理角色RealSubject

public class RealSubjectCglib
{
    public String operate(){
        return "RealSubjectCglib";
    }
}

2 代理類

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyCglib implements MethodInterceptor
{
    private Object target;

    public Object getInstance(Object target)
    {
        this.target = target;
        //Cglib中的加強器,用來建立動態代理
        Enhancer enhancer = new Enhancer();
        //設定要建立動態代理的類
        enhancer.setSuperclass(this.target.getClass());
        //設定回撥,這裡相當於是對於代理類上所有方法的呼叫,都會呼叫Callback,而Callback則需要實現intercept()方法進行攔截
        enhancer.setCallback(this);
        Object obj = enhancer.create();
        return obj;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
    {
        System.out.print("I'm Proxy, I'm invoking...");
        Object object = proxy.invokeSuper(obj, args);
        System.out.println(object);
        return object;
    }
}

測試程式碼:

        ProxyCglib proxy = new ProxyCglib();
        RealSubjectCglib cglib = (RealSubjectCglib)proxy.getInstance(new RealSubjectCglib());
        cglib.operate();

輸出結果:I’m Proxy, I’m invoking…RealSubjectCglib

代理模式的應用形式
1. 遠端代理:即為一個物件在不同的地址空間提供區域性代表,這樣可以隱藏一個物件存在於不同地址空間的事實;
2. 虛擬代理:即根據需要建立開銷很大的物件,通過它來存放例項化需要很長時間的真實物件;
3. 安全代理:用來控制真實物件訪問時的許可權;
4. 智慧指引:即當呼叫真實物件時,代理處理另外一些事。

Jdk中的代理模式:java.lang.reflect.Proxy; RMI

裝飾模式和代理模式的區別
裝飾器模式關注於在一個物件上動態的新增方法,然而代理模式關注於控制對物件的訪問。換句話 說,用代理模式,代理類(proxy class)可以對它的客戶隱藏一個物件的具體資訊。因此,當使用代理模式的時候,我們常常在一個代理類中建立一個物件的例項。並且,當我們使用裝飾器模式的時候,我們通常的做法是將原始物件作為一個引數傳給裝飾者的構造器。

外觀模式和代理模式的區別
代理與外觀的主要區別在於,代理物件代表一個單一物件而外觀物件代表一個子系統,代理的客戶物件無法直接訪問物件,由代理提供單獨的目標物件的訪問,而通常外觀物件提供對子系統各元件功能的簡化的共同層次的呼叫介面。代理是一種原來物件的代表,其他需要與這個物件打交道的操作都是和這個代表交涉的。

介面卡模式和代理模式的區別
介面卡模式改變所考慮的物件的介面,代理模式不能改變所代理物件的介面。

歡迎支援筆者新書:《RabbitMQ實戰指南》以及關注微信公眾號:Kafka技術專欄。
這裡寫圖片描述