java設(shè)計模式-代理模式(Proxy)

定義

代理模式是對象的結(jié)構(gòu)模式。代理模式給某一個對象提供代理對象球及,并由代理對象控制對源對象的引用芋忿。

代理模式的結(jié)構(gòu)

所謂的代理,就是一個人或者一個機構(gòu)代表另外一個人或者另外一個機構(gòu)采取行動绒北。在一些情況下,一個客戶不想或者不能夠直接引用一個對象察署,而代理對象可以在客戶端和目標(biāo)對象中間起到中介的作用闷游。

代理模式類圖如下:

代理模式的類圖

代理模式中的角色有:

  • 抽象對象角色(AbstractObject):聲明了目標(biāo)對象和代理對象的共同接口,這樣依賴在任何可以使用目標(biāo)對象的地方都可以使用代理對象。
  • 目標(biāo)對象角色(RealObject):定義了代理對象所代表的目標(biāo)對象脐往。
  • 代理對象角色(ProxyObject):代理對象內(nèi)部含有目標(biāo)對象的引用休吠,從而可以在任何時候操作目標(biāo)對象;代理對象提供一個與目標(biāo)對象相同的接口业簿,以便可以在任何時候替代目標(biāo)對象瘤礁。代理對象通常在客戶端調(diào)用傳遞給目標(biāo)對象之前或者之后,執(zhí)行某個操作辖源,而不是單純的將調(diào)用傳遞給目標(biāo)對象蔚携。

示例代碼

抽象對象角色

public abstract class AbstractObject {
    /**
     * 定義操作
     */
    public abstract void operation();
}

目標(biāo)對象角色

public class RealObject extends AbstractObject {
    public void operation() {
        System.out.println("Do Something!");
    }
}

代理對象角色

public class ProxyObject extends AbstractObject {
    RealObject realObject = new RealObject();
    public void operation() {
        //在調(diào)用目標(biāo)對象之前,完成一些操作
        System.out.println("Before Do Something");
        realObject.operation();
        //在調(diào)用目標(biāo)對象之后克饶,完成一些操作
        System.out.println("After Do Something");
    }
}

客戶端

public class Client {
    public static void main(String[] args) {
        AbstractObject abstractObject = new ProxyObject();
        abstractObject.operation();
    }
}

從上面的例子可以看出代理對象將客戶端的調(diào)用委派給目標(biāo)對象酝蜒,在調(diào)用目標(biāo)對象的方法之前跟之后都可以執(zhí)行特定的操作。

這就是靜態(tài)代理的實現(xiàn)矾湃,靜態(tài)代理中亡脑,一個目標(biāo)對象對應(yīng)一個代理對象,代理類在編譯時期就已經(jīng)確定了邀跃。

靜態(tài)代理方式總結(jié)

  1. 可以做到在不修改目標(biāo)對象的前提下霉咨,拓展目標(biāo)對象的功能。
  2. 缺點是:因為代理對象需要同目標(biāo)對象實現(xiàn)同樣的接口拍屑,所以會有很多的代理類途戒,造成類過多;并且僵驰,一旦接口中增加方法喷斋,目標(biāo)對象同代理對象都需要進行維護。

解決這個缺點的方式就是使用動態(tài)代理蒜茴。

動態(tài)代理

動態(tài)代理主要有如下特點:

  • 代理對象不需要實現(xiàn)目標(biāo)對象的接口星爪。
  • 代理對象的生成,使用的是Java的API粉私,動態(tài)的在內(nèi)存中構(gòu)件代理對象(這需要我們指定創(chuàng)建代理對象/目標(biāo)對象的接口的類型)顽腾。
  • 動態(tài)代理也叫做JDK代理、接口代理诺核。

JDK中生成代理對象的API

代理類所在的包為:java.lang.reflect.Proxy抄肖。

JDK實現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數(shù)窖杀,源碼中的方法定義為:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    //......
}

注意漓摩,該方法在Proxy類中是靜態(tài)方法,且接收的三個參數(shù)依次為:

  • ClassLoader loader:指定當(dāng)前目標(biāo)對象使用類加載器陈瘦,獲取加載器的方法是固定的。
  • Class<?>[] interfaces:目標(biāo)對象實現(xiàn)的接口類型,使用泛型方式確認(rèn)類型痊项。
  • InvocationHandler h:事件處理锅风。執(zhí)行目標(biāo)對象的方法時,會觸發(fā)事件處理器的方法鞍泉,會把當(dāng)前執(zhí)行目標(biāo)對象的方法作為參數(shù)傳入皱埠。

示例代碼

目標(biāo)對象接口

public interface IUserDao {
    void save();
}

目標(biāo)對象類

public class UserDao implements IUserDao {
    @Override
    public void save() {
        System.out.println("---------已經(jīng)保存數(shù)據(jù)----------");
    }
}

動態(tài)代理對象

/**
 * 創(chuàng)建動態(tài)代理對象
 * 動態(tài)代理對象不需要實現(xiàn)接口,但是需要指定接口類型
 */
public class ProxyFactory {
    //維護一個目標(biāo)對象
    private Object target;
    //對象構(gòu)造時咖驮,提供目標(biāo)對象
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //給目標(biāo)對象生成代理對象
    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("Begin Transaction");
                        //執(zhí)行目標(biāo)對象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("Commit Transaction");
                        return returnValue;
                    }
                });
    }
}

測試類

public class TestProxyFactory {
    public static void main(String[] args) {
        //目標(biāo)對象
        IUserDao userDao = new UserDao();
        //原始類型 class com.sschen.proxy.UserDao
        System.out.println(userDao.getClass());
        
        //給定目標(biāo)對象边器,動態(tài)創(chuàng)建代理對象
        IUserDao proxy = (IUserDao) new ProxyFactory(userDao).getProxyInstance();
        //代理對象類型 class com.sun.proxy.$Proxy0
        System.out.println(proxy.getClass());
        
        proxy.save();
    }
}

從上面的代碼可以看出,動態(tài)代理對象不需要實現(xiàn)目標(biāo)對象接口托修,但是目標(biāo)對象一定要實現(xiàn)接口忘巧,否則不能使用動態(tài)代理。

Cglib代理

上面的靜態(tài)代理和動態(tài)代理模式都需要目標(biāo)對象是一個實現(xiàn)了接口的目標(biāo)對象睦刃,但是有的時候砚嘴,目標(biāo)對象可能只是一個單獨的對象,并沒有實現(xiàn)任何的接口涩拙,這個時候际长,我們就可以使用目標(biāo)對象子類的方式實現(xiàn)代理,這種代理方式就是:Cglib代理

定義

Cglib代理兴泥,也叫做子類代理工育,它是在內(nèi)存中構(gòu)件一個子類對象,從而實現(xiàn)對目標(biāo)對象的功能拓展搓彻。

  • JDK的動態(tài)代理有個限制如绸,就是使用動態(tài)代理的目標(biāo)對象必須實現(xiàn)至少一個接口,由此好唯,沒有實現(xiàn)接口但是想要使用代理的目標(biāo)對象竭沫,就可以使用Cglib代理。
  • Cglib是強大的高性能的代碼生成包骑篙,它可以在運行期間拓展Java類與實現(xiàn)Java接口蜕提。它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop靶端,為他們提供方法的interception(攔截)谎势。
  • Cglib包的底層是通過使用一個小而快的字節(jié)碼處理框架ASM來轉(zhuǎn)換字節(jié)碼并生成新的類,不鼓勵直接只使用ASM杨名,因為它要求你必須對JVM內(nèi)部結(jié)構(gòu)脏榆,包括class文件的格式和指令集都很熟悉。

Cglib子類代理的實現(xiàn)方法

  1. 需要引入Cglib的jar文件台谍,在Maven中可以直接在POM.xml中添加下列引用即可须喂。
        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>
  1. 引入包后,就可以在內(nèi)存中動態(tài)構(gòu)建子類。
  2. 代理的對象不能為final的坞生,否則會報錯仔役。
  3. 目標(biāo)對象的方法如果為final/static修飾的,那么就不會被攔截是己,即不會執(zhí)行目標(biāo)對象額外的方法又兵。

代碼示例

目標(biāo)對象類

public class UserDao {
    public void save() {
        System.out.println("--------已經(jīng)保存數(shù)據(jù)--------");
    }
}

Cglib代理工廠類

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Cglib子類代理工廠
 * 對UserDao對象在內(nèi)存中動態(tài)構(gòu)建出一個子類對象
 */
public class ProxyFactory implements MethodInterceptor {
    //維護目標(biāo)對象
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }

    //獲取目標(biāo)對象的代理對象
    public Object getProxyInstance() {
        //1. 實例化工具類
        Enhancer en = new Enhancer();
        //2. 設(shè)置父類對象
        en.setSuperclass(this.target.getClass());
        //3. 設(shè)置回調(diào)函數(shù)
        en.setCallback(this);
        //4. 創(chuàng)建子類,也就是代理對象
        return en.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Begin Transaction");

        //執(zhí)行目標(biāo)對象的方法
        Object returnValue = method.invoke(target, objects);

        System.out.println("End Transaction");

        return returnValue;
    }
}

測試類

public class TestProxyFactory {
    public static void main (String[] args) {
        //目標(biāo)對象
        UserDao userDao = new UserDao();
        //生成代理對象
        UserDao userDaoProxy = (UserDao) new ProxyFactory(userDao).getProxyInstance();
        //調(diào)用對象方法
        userDaoProxy.save();
    }
}

在Spring的AOP編程中:

  • 如果加入容器的目標(biāo)對象有實現(xiàn)接口卒废,就使用JDK代理
  • 如果目標(biāo)對象沒有實現(xiàn)接口沛厨,就使用Cglib代理。

參考

《JAVA與模式》之代理模式
Java的三種代理模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摔认,一起剝皮案震驚了整個濱河市逆皮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌级野,老刑警劉巖页屠,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蓖柔,居然都是意外死亡辰企,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門况鸣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牢贸,“玉大人,你說我怎么就攤上這事镐捧∏彼鳎” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵懂酱,是天一觀的道長竹习。 經(jīng)常有香客問我,道長列牺,這世上最難降的妖魔是什么整陌? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮瞎领,結(jié)果婚禮上泌辫,老公的妹妹穿的比我還像新娘。我一直安慰自己九默,他們只是感情好震放,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驼修,像睡著了一般殿遂。 火紅的嫁衣襯著肌膚如雪诈铛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天墨礁,我揣著相機與錄音癌瘾,去河邊找鬼。 笑死饵溅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妇萄。 我是一名探鬼主播蜕企,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冠句!你這毒婦竟也來了轻掩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤懦底,失蹤者是張志新(化名)和其女友劉穎唇牧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聚唐,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡丐重,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杆查。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扮惦。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亲桦,靈堂內(nèi)的尸體忽然破棺而出崖蜜,到底是詐尸還是另有隱情,我是刑警寧澤客峭,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布豫领,位于F島的核電站,受9級特大地震影響舔琅,放射性物質(zhì)發(fā)生泄漏等恐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一搏明、第九天 我趴在偏房一處隱蔽的房頂上張望鼠锈。 院中可真熱鬧,春花似錦星著、人聲如沸购笆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽同欠。三九已至样傍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铺遂,已是汗流浹背衫哥。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留襟锐,地道東北人撤逢。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像粮坞,于是被迫代替她去往敵國和親蚊荣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容