設計模式之代理模式


一、代理模式

1.代理模式簡介:

代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問。使用代理模式創建代理對象,讓代理對象控制目標對象的訪問(目標對象可以是遠程的對象、創建開銷大的對象或需要安全控制的對象),並且可以在不改變目標對象的情況下添加一些額外的功能。

所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之前起到中介的作用。
代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。

2.代理模式涉及到的角色:

(1)抽象角色聲明真實對象和代理對象的共同接口;可以是抽象類,也可以是接口,是一個最普通的業務類型定義,無特殊要求。

(2)代理角色:代理對象角色內部含有對真實對象的引用,從而可以操作真實對象,同時代理對象提供與真實對象相同的接口以便在任何時刻都能代替真實對象。同時,代理對象可以在執行真實對象操作時,附加其他的操作,相當於對真實對象進行封裝。

  也叫委托類、代理類。它把所有抽象角色定義的方法給真實角色實現,並且在真實角色處理完畢前后做預處理和善后工作。(最簡單的比如打印日志)(自己並未實現業務邏輯接口,而是調用真實角色來實現。)

(3)真實角色:代理角色所代表的真實對象,是我們最終要引用的對象。也叫被委托角色、被代理角色。是業務邏輯的具體執行者。(真正實現了業務邏輯接口。)

示意圖:

代理模式是常用的java設計模式,如上圖所示,代理類和實現類實現了同一個接口,而代理類中則具有現有對象,通過代理類去操作現有對象,那么我們就可以在代理類中增加很多操作,包括前置增強、后置增強、環繞增強、異常增強等,這也就能解決我們實際當中遇到的一些問題,例如延遲對象的創建、控制對象的訪問等。

代理模式的特征是代理類與委托類有同樣的接口。代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后處理消息等。
代理類與委托類之間通常會存在關聯關系,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。

3.分類

按照代理的創建時期,代理類可以分為兩種。
靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
動態代理:在程序運行時,運用反射機制動態創建而成。

二、靜態代理

     靜態代理是由程序員創建或特定工具自動生成源代碼,再對其編譯。最大的特點就是在程序運行時代理類的.class文件就已經存在了,但是這有一個很大的缺陷即每一個代理類只能為一個接口服務。靜態代理的實現如下:

首先定義一個接口(抽象角色),里面有一個print()方法:

package com.proxyPattern.staticProxy;
/**
 * 抽象角色
 * @author lxx
 *
 */
public interface StaticHelloWorld {
    //打印方法
    void print();
}

 真實角色:

package com.proxyPattern.staticProxy;
/**
 * 真實角色
 * @author lxx
 *
 */
public class StaticHelloWorldImpl implements StaticHelloWorld{
    
    public void print(){
        System.out.println("Hello World!");
    }
}

代理角色:注意,這里的重點是代理對象和實際對象實現的是同一個接口因為希望在任何時候讓代理對象替代實際對象

package com.proxyPattern.staticProxy;

/**
 * 代理角色
 * @author lxx
 *
 */
public class StaticProxy implements StaticHelloWorld{
    //代理類中含有對真實對象的引用
    private StaticHelloWorld staticHelloWorld;
    
    public StaticProxy(StaticHelloWorld staticHelloWorldImpl){
        this.staticHelloWorld=staticHelloWorldImpl;
    }
    
    public void print(){
        System.out.println("Before Hello World!");
        staticHelloWorld.print();
        System.out.println("After Hello World!");
    }
}

測試類:寫一個類去調用代理對象,在代理對象的構造函數中傳入一個實際對象即可

package com.proxyPattern.staticProxy;

public class StaticTestMain {
    public static void main(String[] args){
        StaticHelloWorld shw=new StaticHelloWorldImpl();
        StaticProxy sp=new StaticProxy(shw);
        sp.print();
    }
}

運行結果:

Before Hello World!
Hello World!
After Hello World!

靜態代理的缺點
靜態代理的特點是靜態代理的代理類是程序員創建的,在程序運行之前靜態代理的.class文件已經存在了。
從靜態代理來看,看到靜態代理模式確實可以有一個代理對象來控制實際對象的引用,並通過代理對象來使用實際對象。這種模式在代理量較小的時候還可以,但是代理量一大起來,就存在着三個比較大的缺點:
(1)、如果想換一種代理內容,比如我在"Hello World"前后不想輸入"Before XXX"和"After XXX"了,想輸出運行前后系統當前時間,就必須新寫一個代理對象。這樣很容易造成代理對象的膨脹。
(2)、每一個代理類只能為一個接口服務,並且代理內容無法復用,也就是說"Before XXX"和"After XXX"只可以給某一個類使用,另一個類如果也想使用這個代理內容,必須自己也寫一個,同樣,造成的后果就是代理類的無限膨脹
(3)、接口里面如果新增了一個方法,實際對象實現了這個方法,代理對象也必須新增內容,去給這個新增方法增加代理內容(假如需要的話)

靜態代理類優缺點
優點:業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
缺點:
1)代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。

如果要按照上述的方式(靜態代理)使用代理模式,那么真實角色必須是實現已經存在的,並將其作為代理對象的內部屬性。
但是實際使用時,一個真實角色必須對應一個代理角色,但如果大量使用會導致類的急劇膨脹;此外,如果事先並不知道真實角色,該如何使用代理呢?這個問題可以通過Java的動態代理類來解決。

三、動態代理

動態代理:在程序運行時,運用反射機制動態創建代理類(Proxy)而成。

動態代理類說明
所謂Dynamic Proxy是這樣一種class:
它是在運行時生成的class,在生成它時你必須提供一組interface給它,然后該class就宣稱它實現了這些interface。
你當然可以把該class的實例當作這些interface中的任何一個來用。
當然,這個Dynamic Proxy其實就是一個Proxy,它不會替你做實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作
在使用動態代理類時,我們必須實現InvocationHandler接口。每一個動態代理類都會有一個與之關聯的invocation handler。
真正的調用是在invocation handler的invoke()方法里完成的。

Java動態代理類位於java.lang.reflect包下,一般主要涉及到以下兩個類:
1、Interface InvocationHandler:InvocationHandler 接口為方法調用接口,它聲明了負責調用任意一個方法的invoke()方法。(InvocationHandler 是代理實例的調用處理程序實現的接口。invoke()在代理實例上處理方法調用並返回結果。)
該接口中僅定義了一個方法:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

Object proxy:指動態代理類實例
Method method:要調用的方法
Object[] args:方法調用時所需要參數

2、Proxy(Proxy 提供用於創建動態代理類及其實例的靜態方法.)
(1)getProxyClass()靜態方法負責創建動態代理類,它的完整定義如下:

public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces) throws IllegalArgumentException

參數loader 指定動態代理類的類加載器,參數interfaces 指定動態代理類需要實現的所有接口。

(2)newProxyInstance()靜態方法負責創建動態代理類的實例,它的完整定義如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

CLassLoader loader:類的加載器 (指定動態代理類的類加載器)
Class<?> interfaces:得到全部的接口 (指定動態代理類需要實現的所有接口)
InvocationHandler h:得到InvocationHandler接口的子類的實例 (指定與動態代理類關聯的 InvocationHandler 對象)

假設有個Foo接口,具體定義不寫了,以下兩種方式都創建了實現Foo接口的動態代理類的實例:

方式一:

/**** 方式一 ****/
//創建InvocationHandler對象
InvocationHandler handler = new MyInvocationHandler(...);
//創建動態代理類
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });
//創建動態代理類的實例
Foo foo = (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });

方式二:

/**** 方式二 ****/
//創建InvocationHandler對象
InvocationHandler handler = new MyInvocationHandler(...);
//直接創建動態代理類的實例
Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class }, handler);

由Proxy類的靜態方法創建的動態代理類的實例具有以下特點:
(1). 假定變量foo 是一個動態代理類的實例,並且這個動態代理類實現了Foo 接口,那么"foo instance of Foo"的值為true。把變量foo強制轉換為Foo類型是合法的:(Foo) foo //合法
(2). 每個動態代理類實例都和一個InvocationHandler 實例關聯。Proxy 類的getInvocationHandler(Object proxy)靜態方法返回與參數proxy指定的代理類實例所關聯的InvocationHandler 對象。
(3). 假定Foo接口有一個add()方法,那么當程序調用動態代理類實例foo的add()方法時,該方法會調用與它關聯的InvocationHandler 對象的invoke()方法。

來看一個例子:

首先還是定義一個接口(抽象角色):

package com.proxyPattern.dynamicProxy;

public interface DynamicHelloWorld {
    String print();
}

 寫一個類去實現它(真實角色):

package com.proxyPattern.dynamicProxy;

public class DynamicHelloWorldImpl implements DynamicHelloWorld{
    
    public String print(){
        System.out.println("Enter DynamicHelloWorldImpl.print()");
        return "DynamicHelloWorldImpl";
    }
}

之后定義一個動態代理類,可以像下面這樣,讓動態代理類在外面生成,只在構造函數中傳入一個target:

package com.proxyPattern.dynamicProxy;

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

/**
 * 該代理類的內部屬性是Object類型,實際使用的時候通過該類的構造方法傳遞進來一個對象。
 * 該類實現了invoke()方法,該方法中的method.invoke()其實就是調用被代理對象的將要執行的方法,
 * 通過動態代理類,我們可以在執行真實對象的方法前后加入自己的一些額外方法
 *
 */
public class DynamicProxy implements InvocationHandler{
    
    //對真實對象的引用
    private Object target;
    
    public DynamicProxy(Object target){
        this.target=target;
    }
    
    public Object invoke(Object proxy,Method method,Object[] args) 
        throws Throwable{
        //目標方法之前執行
        System.out.println("Before DynamicProxy");
        //通過反射機制來調用目標類方法
        method.invoke(target, args);
        //目標方法之后執行
        System.out.println("After DynamicProxy");
        return null;
    }
}

測試類:

package com.proxyPattern.dynamicProxy;

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

public class DynamicTestMain {
    
    public static void main(String[] args){
        DynamicHelloWorld dhwi=new DynamicHelloWorldImpl();
        InvocationHandler ih=new DynamicProxy(dhwi);
        //創建代理實例(使用Proxy類和自定義的調用處理邏輯(handler)來生成一個代理對象)
        DynamicHelloWorld dhw=(DynamicHelloWorld)Proxy.
            newProxyInstance(DynamicHelloWorld.class.getClassLoader(), new Class<?>[]{DynamicHelloWorld.class}, ih);
        //調用方法時,轉移給handler接管,由其中的invoke()方法實際完成方法執行
        dhw.print();
    }
}

也可以像下面這樣,把newInstance即生成一個動態代理類的過程放到InvocationHandler的實現類中:

package com.proxyPattern.dynamicProxy;

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

public class DynamicProxy1 implements InvocationHandler{
    
    private Object target;
    
    public Object newInstance(Object target){
        this.target = target;
        //創建代理類實例
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), this);
    }
    
    public Object invoke(Object proxy,Method method,Object[] args) 
            throws Throwable{
         //目標方法之前執行
        System.out.println("Before DynamicProxy");
        //通過反射機制來調用目標類方法
        method.invoke(target, args);
        //目標方法之后執行
        System.out.println("After DynamicProxy");
        return null;
    }
}

測試類:

package com.proxyPattern.dynamicProxy;

public class DynamicTestMain1 {
    
    public static void main(String[] args){
        DynamicProxy1 dp = new DynamicProxy1();
        DynamicHelloWorld dhwi = new DynamicHelloWorldImpl();
        DynamicHelloWorld dhw = (DynamicHelloWorld)dp.newInstance(dhwi);
        dhw.print();
    }
    
}

不管哪種寫法,運行結果都是一樣的:
Before DynamicProxy
Enter DynamicHelloWorldImpl.print()
After DynamicProxy

首先創建委托類對象,將其以構造函數傳入代理處理器,代理處理器DynamicProxy中會以Java反射方式調用該委托類對應的方法。然后使用Java反射機制中的Proxy.newProxyInstance方式創建一個代理類實例,創建該實例需要指定該實例的類加載器,需要實現的接口(即目標接口),以及處理代理實例接口調用的處理器。

最后,調用代理類目標接口方法時,會自動將其轉發到代理處理器中的invoke方法內,invoke方法內部實現預處理,對委托類方法調用,事后處理等邏輯。

總結:
1.創建動態代理步驟:
(1).創建一個實現接口InvocationHandler的類,它必須實現invoke()方法。
(2).創建被代理的類以及接口。
(3).通過Proxy的靜態方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)創建一個代理。
(4).通過代理調用方法。

2.動態代理,利用動態編譯+反射技術,把對實際對象的方法調用轉換成對傳入的InvocationHandler接口實現類的invoke方法的調用,這是動態代理模式實現的關鍵點。

動態代理的優點
(1)、最直觀的,類少了很多。
(2)、代理內容也就是InvocationHandler接口的實現類可以復用,可以給A接口用、也可以給B接口用,A接口用了InvocationHandler接口實現類A的代理,不想用了,可以方便地換成InvocationHandler接口實現B的代理。
(3)、與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。
(4)、最重要的,用了動態代理,就可以在不修改原來代碼的基礎上,就在原來代碼的基礎上做操作,這就是AOP即面向切面編程。

動態代理的缺點
動態代理有一個最大的缺點,就是它只能針對接口生成代理,不能只針對某一個類生成代理,比方說我們在調用Proxy的newProxyInstance方法的時候,第二個參數傳某個具體類的getClass(),那么會報錯:
Exception in thread "main" java.lang.IllegalArgumentException: proxy.DynamicHelloWorldImpl is not an interface
這是因為java.lang.reflect.Proxy的newProxyInstance方法會判斷傳入的Class是不是一個接口。

而實際使用中,我們為某一個單獨的類實現一個代理也很正常,這種情況下,我們就可以考慮使用CGLIB(一種字節碼增強技術)來為某一個類實現代理了。


免責聲明!

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



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