設(shè)計(jì)模式---代理模式

代理(Proxy)是一種常用的行為型設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪(fǎng)問(wèn)方式;即通過(guò)代理對(duì)象訪(fǎng)問(wèn)目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能。比如日志嫩絮,統(tǒng)計(jì)操作,常用框架中Mybaits中的Mapper就是通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)方法的調(diào)用的。

如圖所示代理模式是對(duì)目標(biāo)對(duì)象的封裝和隱藏,通過(guò)關(guān)聯(lián)目標(biāo)對(duì)象,并向外暴露于目標(biāo)接口同樣的行為方法弱左,從而通過(guò)內(nèi)部完成對(duì)目標(biāo)對(duì)象的增強(qiáng)。


一炕淮、靜態(tài)代理

靜態(tài)代理要求被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類(lèi)拆火,一下以打印日志為例實(shí)現(xiàn)靜態(tài)代理

    1. 公共接口,定義用戶(hù)相關(guān)的業(yè)務(wù)邏輯
/**
 * @author haopeng
 * @date 2020-08-11 15:14
 */
public interface BizService {

    void doSomething();
}
  • 2.具體實(shí)現(xiàn)類(lèi)涂圆,也就是被代理的目標(biāo)對(duì)象
public class UserServiceImpl implements BizService {
    @Override
    public void doSomething() {
        System.out.println("執(zhí)行用戶(hù)相關(guān)業(yè)務(wù)邏輯");
    }
}
  • 3.代理類(lèi)们镜,對(duì)目標(biāo)對(duì)象進(jìn)行增強(qiáng)
public class UserLogProxy implements BizService {

    private final BizService service = new UserServiceImpl();

    @Override
    public void doSomething() {
        System.out.println("靜態(tài)代理執(zhí)行前。润歉。模狭。打印日志 。踩衩。嚼鹉。。" );
        service.doSomething();
        System.out.println("靜態(tài)代理執(zhí)行后驱富。反砌。。 打印日志 萌朱。宴树。。晶疼。" );
    }
}
  • 4.測(cè)試
public class Client {

    public static void main(String[] args) {
        UserLogProxy userLogProxy = new UserLogProxy();
        userLogProxy.doSomething();
    }
}

程序運(yùn)行結(jié)果:

靜態(tài)代理執(zhí)行前酒贬。。翠霍。打印日志 锭吨。。寒匙。零如。
執(zhí)行用戶(hù)相關(guān)業(yè)務(wù)邏輯
靜態(tài)代理執(zhí)行后躏将。。考蕾。 打印日志 祸憋。。肖卧。蚯窥。

可以看到完成了對(duì)用戶(hù)業(yè)務(wù)方法的日志打印功能,但是動(dòng)態(tài)代理明顯的區(qū)別就是:需要再編譯器確定被代理對(duì)象塞帐,試想一下拦赠,如果還有訂單、商品等業(yè)務(wù)方法也需要打印日志的話(huà)葵姥,那么我們就需要對(duì)每一個(gè)業(yè)務(wù)類(lèi)編寫(xiě)對(duì)應(yīng)的代理類(lèi)實(shí)現(xiàn)荷鼠。

二、動(dòng)態(tài)代理

利用反射機(jī)制在運(yùn)行時(shí)創(chuàng)建代理類(lèi)榔幸。動(dòng)態(tài)代理的實(shí)現(xiàn)方式有兩種分別是jdk動(dòng)態(tài)代理cglib動(dòng)態(tài)代理

1.jdk動(dòng)態(tài)代理

jdk動(dòng)態(tài)代理的實(shí)現(xiàn)步驟:

  1. 通過(guò)實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器允乐;
  2. 通過(guò)為 Proxy 類(lèi)指定 ClassLoader 對(duì)象和一組 interface 來(lái)創(chuàng)建動(dòng)態(tài)代理類(lèi);
  3. 通過(guò)反射機(jī)制獲得動(dòng)態(tài)代理類(lèi)的構(gòu)造函數(shù)牡辽,其唯一參數(shù)類(lèi)型是調(diào)用處理器接口類(lèi)型喳篇;
  4. 通過(guò)構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類(lèi)實(shí)例,構(gòu)造時(shí)調(diào)用處理器對(duì)象作為參數(shù)被傳入态辛。
  • 代碼實(shí)現(xiàn)
public interface UserBizService {

    void doUserSomething();
}
public class UserServiceImpl implements UserBizService {
    @Override
    public void doUserSomething() {
        System.out.println("執(zhí)行商用戶(hù)相關(guān)的業(yè)務(wù)邏輯麸澜。。奏黑。");
    }
}
  • 通過(guò)實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器
public class LogProxy implements InvocationHandler {

    private Object object;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("動(dòng)態(tài)代理執(zhí)行前炊邦。。熟史。打印日志");
        System.out.println( "執(zhí)行方法" + method.getName());
        Object invoke = method.invoke(object, args);
        System.out.println("動(dòng)態(tài)代理執(zhí)行后馁害。。蹂匹。打印日志");
        return invoke;
    }
}
public class Client {

    public static void main(String[] args) {
        UserBizService hello = new UserServiceImpl();
        InvocationHandler invocationHandler = new LogProxy(hello);
        UserBizService userBizService = (UserBizService) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), invocationHandler);
        userBizService.doUserSomething();


    }
}

執(zhí)行結(jié)果

動(dòng)態(tài)代理執(zhí)行前碘菜。。限寞。打印日志
執(zhí)行方法doUserSomething
執(zhí)行商用戶(hù)相關(guān)的業(yè)務(wù)邏輯忍啸。。履植。
動(dòng)態(tài)代理執(zhí)行后计雌。。玫霎。打印日志

可以看到符合預(yù)期效果凿滤,現(xiàn)在如果想新增一個(gè)代理功能妈橄,比如為商品業(yè)務(wù)方法打印日志,我們只需要新增幾行代碼就可以搞定翁脆,不需要額外的為商品服務(wù)單獨(dú)創(chuàng)建代理類(lèi)的實(shí)現(xiàn)

  • 用到的基礎(chǔ)服務(wù)類(lèi)
public interface GoodService {

    public void doGoodThing();
}

public class GoodServiceImpl implements GoodService {

    @Override
    public void doGoodThing() {
        System.out.println("執(zhí)行商品相關(guān)的業(yè)務(wù)邏輯眷蚓。。鹃祖。");
    }
}
  • 測(cè)試類(lèi)中通過(guò)構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類(lèi)實(shí)例
public class Client {

    public static void main(String[] args) {
        UserBizService userService = new UserServiceImpl();
        InvocationHandler invocationHandler = new LogProxy(userService);
        // 以下代碼執(zhí)行會(huì)直接報(bào)錯(cuò)溪椎,因?yàn)閖dk的動(dòng)態(tài)代理是能代理接口類(lèi)
        //HelloInterface helloInterface = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), new Class[]{hello.getClass()}, invocationHandler);
        UserBizService userBizService = (UserBizService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
        userBizService.doUserSomething();

        GoodService goodservice = new GoodServiceImpl();
        InvocationHandler invocationHandlerBye = new LogProxy(goodservice);
        GoodService goodService = (GoodService) Proxy.newProxyInstance(goodservice.getClass().getClassLoader(), goodservice.getClass().getInterfaces(), invocationHandlerBye);

        goodService.doGoodThing();

    }
}
  • 運(yùn)行結(jié)果
動(dòng)態(tài)代理執(zhí)行前普舆。恬口。。打印日志
執(zhí)行方法doUserSomething
執(zhí)行商用戶(hù)相關(guān)的業(yè)務(wù)邏輯沼侣。祖能。。
動(dòng)態(tài)代理執(zhí)行后蛾洛。养铸。。打印日志
動(dòng)態(tài)代理執(zhí)行前轧膘。钞螟。。打印日志
執(zhí)行方法doGoodThing
執(zhí)行商品相關(guān)的業(yè)務(wù)邏輯谎碍。。。
動(dòng)態(tài)代理執(zhí)行后蹋半。若债。。打印日志

總結(jié): jdk動(dòng)態(tài)代理要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口的目標(biāo)對(duì)象,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒(méi)有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候就可以使用以目標(biāo)對(duì)象子類(lèi)的方式類(lèi)實(shí)現(xiàn)代理,這種動(dòng)態(tài)代理就叫Cglib

2.cglib動(dòng)態(tài)代理

Cglib代理,也叫作子類(lèi)代理,它是在內(nèi)存中構(gòu)建一個(gè)子類(lèi)對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能的擴(kuò)展.

示例代碼:

  • 代理類(lèi)實(shí)現(xiàn)MethodInterceptor接口
public class CglibLogProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        System.out.println(method.getName());
        Object o1 = methodProxy.invokeSuper(o, args);
        System.out.println("++++++after " + methodProxy.getSuperName() + "++++++");
        return o1;
    }
}
  • 業(yè)務(wù)類(lèi)(被代理類(lèi))熔任,UserBiz不用實(shí)現(xiàn)任何接口
public class UserBiz {

    public String getInfo(String msg) {
        System.out.println("=======執(zhí)行業(yè)務(wù)方法======");
        System.out.println("=======方法參數(shù)為: <" + msg + ">=======");
        return "=======返回業(yè)務(wù)方法執(zhí)行完畢的返回值======";
    }
}
  • 測(cè)試類(lèi)
public class Client {

    public static void main(String[] args) {
        CglibLogProxy cglibProxy = new CglibLogProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserBiz.class);
        enhancer.setCallback(cglibProxy);
        UserBiz o = (UserBiz)enhancer.create();
        String info = o.getInfo("路漫漫其修遠(yuǎn)兮");
        System.out.println(info);

    }

}
  • 執(zhí)行結(jié)果
++++++before CGLIB$getInfo$0++++++
getInfo
=======執(zhí)行業(yè)務(wù)方法======
=======方法參數(shù)為: <路漫漫其修遠(yuǎn)兮>=======
++++++after CGLIB$getInfo$0++++++
=======返回業(yè)務(wù)方法執(zhí)行完畢的返回值======

可以看到同樣完成了代理功能褒链,其實(shí)Spring框架中AOP就是根據(jù)目標(biāo)類(lèi)進(jìn)行判斷,從而決定使用哪種代理方式疑苔,如果目標(biāo)類(lèi)實(shí)現(xiàn)了接口就通過(guò)jdk方式來(lái)代理甫匹,否則通過(guò)cglib方式來(lái)代理。

優(yōu)化改進(jìn)

以上的寫(xiě)法可以結(jié)合工廠(chǎng)模式和泛型的思想再優(yōu)化一下惦费,這樣沒(méi)增加一個(gè)代理類(lèi)就不必寫(xiě)重復(fù)的代碼

/**
 * @author haopeng
 * @date 2020-08-12 10:47
 */
public class ProxyFactory implements MethodInterceptor {

    @SuppressWarnings("unchecked")
    public <T> T getProxyInstance(Class<?> clz) {
        Enhancer en = new Enhancer();
        en.setSuperclass(clz);
        en.setCallback(this);
        return (T)en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法開(kāi)始了 .....");
        //Object invoke = method.invoke(target, args);
        Object invoke = methodProxy.invokeSuper(obj, args);
        System.out.println("方法結(jié)束了 .....");
        return invoke;
    }


}
  • 測(cè)試
public class Client {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        UserBiz userBizProxy = proxyFactory.getProxyInstance(UserBiz.class);
        userBizProxy.getInfo("好玩的cglib動(dòng)態(tài)代理");
    }
}
  • 結(jié)果
方法開(kāi)始了 .....
=======執(zhí)行業(yè)務(wù)方法======
=======方法參數(shù)為: <好玩的cglib動(dòng)態(tài)代理>=======
方法結(jié)束了 .....

總結(jié):

  • JDK的動(dòng)態(tài)代理有一個(gè)限制,就是使用動(dòng)態(tài)代理的對(duì)象必須實(shí)現(xiàn)一個(gè)或多個(gè)接口,如果想代理沒(méi)有實(shí)現(xiàn)接口的類(lèi),就可以使用Cglib實(shí)現(xiàn).
  • Cglib是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展java類(lèi)與實(shí)現(xiàn)java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP,為他們提供方法的interception(攔截)
  • Cglib包的底層是通過(guò)使用一個(gè)小而塊的字節(jié)碼處理框架ASM來(lái)轉(zhuǎn)換字節(jié)碼并生成新的類(lèi).不鼓勵(lì)直接使用ASM,因?yàn)樗竽惚仨殞?duì)JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉
    補(bǔ)充一點(diǎn)ASM技術(shù)在SpringMVC中也有用到兵迅,SpringMVC中填充Controller參數(shù)的時(shí)候獲取參數(shù)的名稱(chēng)就是通過(guò)ASM技術(shù),因?yàn)榉瓷涫遣荒苣玫絽?shù)名的

三趁餐、Mybatis中的動(dòng)態(tài)代理

在使用Mybatis時(shí)我們只需要聲明Mapper接口喷兼,但是并沒(méi)有編寫(xiě)具體的實(shí)現(xiàn)類(lèi),那么Mybaits最終是怎樣執(zhí)行Mapper中的方法的后雷,其實(shí)就是通過(guò)JDK動(dòng)態(tài)代理,生成代理對(duì)象季惯。
源碼示例:
通過(guò)SqlSession獲取UserMapper代理對(duì)象

 SqlSession session = new DefaultSqlSession(null, null, true);
 session.getMapper(UserMapper.class);

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      //   獲取代理工廠(chǎng)
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
              // 通過(guò)Mapper代理工廠(chǎng)生成代理類(lèi)
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

  • MapperProxyFactory#newInstance
// 通過(guò)jdk動(dòng)態(tài)代理實(shí)現(xiàn)生成代理對(duì)象
 protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

// 先生成MapperProxy代理類(lèi)
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吠各,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子勉抓,更是在濱河造成了極大的恐慌贾漏,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藕筋,死亡現(xiàn)場(chǎng)離奇詭異纵散,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)隐圾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)伍掀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人暇藏,你說(shuō)我怎么就攤上這事蜜笤。” “怎么了盐碱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵把兔,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我瓮顽,道長(zhǎng)县好,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任暖混,我火速辦了婚禮缕贡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘儒恋。我一直安慰自己善绎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布诫尽。 她就那樣靜靜地躺著禀酱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牧嫉。 梳的紋絲不亂的頭發(fā)上剂跟,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音酣藻,去河邊找鬼曹洽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辽剧,可吹牛的內(nèi)容都是我干的送淆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怕轿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼偷崩!你這毒婦竟也來(lái)了辟拷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤阐斜,失蹤者是張志新(化名)和其女友劉穎衫冻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谒出,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隅俘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笤喳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片为居。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖莉测,靈堂內(nèi)的尸體忽然破棺而出颜骤,到底是詐尸還是另有隱情唧喉,我是刑警寧澤捣卤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站八孝,受9級(jí)特大地震影響董朝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜干跛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一子姜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楼入,春花似錦哥捕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至阐肤,卻和暖如春凫佛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孕惜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工愧薛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衫画。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓毫炉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親削罩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞄勾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355