Spring框架 之 代理模式

在開發(fā)過程中掌腰,我們常需要在不更改原代碼的前提下實(shí)現(xiàn)業(yè)務(wù)的擴(kuò)展(也就是我們常說的OCP原則)播瞳,為此交汤,就需要用到代理模式來進(jìn)行開發(fā)少梁。
常用的代理模式有兩種,靜態(tài)代理和動(dòng)態(tài)代理炎码,而常用的動(dòng)態(tài)代理技術(shù)又包括JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理郑叠。

一、靜態(tài)代理
我們先來編寫一個(gè)業(yè)務(wù)接口:
package proxy.part01;
/**

  • 核心業(yè)務(wù)接口
    /
    public interface HelloService {
    void sayHello(String msg);
    }
    編寫實(shí)現(xiàn)類:
    package proxy.part01;
    /
    *
  • 核心業(yè)務(wù)的實(shí)現(xiàn)類
    /
    public class HelloServiceImpl implements HelloService{
    //核心業(yè)務(wù)
    public void sayHello(String msg) {
    System.out.println("hello "+msg);
    };
    }
    現(xiàn)在,我們要在不修改HelloServiceImpl的前提下添加新的業(yè)務(wù)排作,例如牵啦,要在核心業(yè)務(wù)的前后輸出當(dāng)前時(shí)間。很容易想到妄痪,我們可以再寫一個(gè)新的實(shí)現(xiàn)類哈雏,這個(gè)新實(shí)現(xiàn)類也實(shí)現(xiàn)HelloService接口,并且實(shí)現(xiàn)sayHello方法衫生,在新的實(shí)現(xiàn)方法中添加新的業(yè)務(wù)裳瘪,并且調(diào)用HelloServiceImpl類的sayHello方法,這個(gè)新的實(shí)現(xiàn)類就是我們說的“代理類(代理對(duì)象)”障簿。
    package proxy.part01;
    /
    *
  • 代理對(duì)象
    /
    public class HelloServiceProxy implements HelloService{
    //創(chuàng)建依賴關(guān)系
    private HelloService helloService;
    public HelloServiceProxy(HelloService helloService) {
    this.helloService=helloService;
    }
    //重新實(shí)現(xiàn)業(yè)務(wù)方法
    public void sayHello(String msg) {
    //添加擴(kuò)展業(yè)務(wù)
    System.out.println("method.start:"+System.nanoTime());
    //核心業(yè)務(wù)
    helloService.sayHello(msg);
    //添加拓展業(yè)務(wù)
    System.out.println("method.down:"+System.nanoTime());
    }
    }
    測(cè)試代理類:
    package proxy.part01;
    /
    *
  • 測(cè)試代理
    /
    public class TestProxy01 {
    public static void main(String[] args) {
    //目標(biāo)對(duì)象
    HelloService helloService;
    //目標(biāo)對(duì)象(被代理類)
    helloService=new HelloServiceImpl();
    //代理對(duì)象(擴(kuò)展的類)
    helloService=new HelloServiceProxy(helloService);
    //調(diào)用業(yè)務(wù)方法
    helloService.sayHello("我是核心業(yè)務(wù)");
    }
    }
    輸出結(jié)果:
    method.start:1838981357912485
    hello 我是核心業(yè)務(wù)
    method.down:1838981358133359
    輸出結(jié)果顯示我們?cè)诓恍薷脑瓕?shí)現(xiàn)類的情況下盹愚,成功為核心業(yè)務(wù)添加了擴(kuò)展業(yè)務(wù)。但是站故,如果我們要為很多個(gè)類或者方法添加擴(kuò)展業(yè)務(wù)皆怕,例如輸出日志、權(quán)限檢查等西篓,我們就要為所有類都創(chuàng)建一個(gè)代理對(duì)象愈腾,這無疑會(huì)造成大量重復(fù)的代碼片段,并且維護(hù)難度也很大岂津。所以我們希望能夠?qū)崿F(xiàn)動(dòng)態(tài)地為不同的類創(chuàng)建代理對(duì)象虱黄,這是便要用到動(dòng)態(tài)代理技術(shù)。
    二吮成、動(dòng)態(tài)代理
    動(dòng)態(tài)代理技術(shù)有兩種常用的實(shí)現(xiàn)方式:基于反射機(jī)制實(shí)現(xiàn)的JDK動(dòng)態(tài)代理和直接生成字節(jié)碼文件的CGLIB動(dòng)態(tài)代理橱乱。
    1.JDK動(dòng)態(tài)代理
    先寫一個(gè)核心業(yè)務(wù)接口:
    package proxy.part02;
    /
    *
  • 核心業(yè)務(wù)接口
    /
    public interface OrgSerivce {
    //保存一個(gè)組織
    void saveOrg(String org);
    //根據(jù)ID更新一個(gè)組織
    int updateOrg(Integer id);
    }
    實(shí)現(xiàn)核心業(yè)務(wù)接口:
    package proxy.part02;
    /
    *
  • 實(shí)現(xiàn)核心業(yè)務(wù)接口
    */
    public class OrgServiceImpl implements OrgSerivce {
    @Override
    public void saveOrg(String org){
    System.out.println("save org "+org);
    }
    @Override
    public int updateOrg(Integer id) {
    System.out.println("update org which`s id is "+id);
    return 1;
    }
    }
    我們還需要再編寫一個(gè)用于處理擴(kuò)展業(yè)務(wù)的類:
    package proxy.part02;

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

  • 擴(kuò)展業(yè)務(wù)的處理對(duì)象,要實(shí)現(xiàn)Java反射包中的InvocationHandler接口
    /
    class ServiceHandler implements InvocationHandler{
    //定義目標(biāo)對(duì)象的引用粱甫,并建立聯(lián)系
    private Object target;
    public ServiceHandler(Object target) {
    this.target=target;
    }
    /
    *執(zhí)行代理對(duì)象的業(yè)務(wù)方法時(shí),會(huì)自動(dòng)執(zhí)行這個(gè)handler對(duì)象的invoke方法
    • @param proxy 指向代理對(duì)象
    • @param method 指向接口中的方法對(duì)象
    • @param args 指向method對(duì)象執(zhí)行時(shí)需要的實(shí)際參數(shù)
    • */
      @Override
      public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
      System.out.println("begin transaction");
      //執(zhí)行目標(biāo)對(duì)象的method(核心業(yè)務(wù))方法,參數(shù)為args
      Object result=method.invoke(target,args);
      System.out.println("end transaction");
      return result;
      }
      }
      測(cè)試JDK動(dòng)態(tài)代理:
      package proxy.part02;

import java.lang.reflect.Proxy;
/**

  • 測(cè)試JDK動(dòng)態(tài)代理
    /
    public class TestDynamicProxy {
    public static void main(String[] args) {
    //創(chuàng)建一個(gè)目標(biāo)對(duì)象
    OrgSerivce orgService=new OrgServiceImpl();
    //獲得這個(gè)目標(biāo)對(duì)象的類加載器對(duì)象
    ClassLoader loader=orgService.getClass().getClassLoader();
    //獲得目標(biāo)對(duì)象所實(shí)現(xiàn)的接口的Class對(duì)象
    Class<?>[] interfaces=orgService.getClass().getInterfaces();
    //創(chuàng)建一個(gè)Handler對(duì)象泳叠,處理擴(kuò)展業(yè)務(wù)
    ServiceHandler handler=new ServiceHandler(orgService);
    /
    *
    * 通過Proxy.newProxyInstance方法獲得代理對(duì)象
    * @param loader 目標(biāo)對(duì)象的類加載器對(duì)象
    * @param interfaces 目標(biāo)對(duì)象實(shí)現(xiàn)的所有接口的Class對(duì)象數(shù)組
    * @param handler 處理擴(kuò)展業(yè)務(wù)的對(duì)象
    */
    orgService=(OrgSerivce)Proxy.newProxyInstance(loader,interfaces,handler);
    //執(zhí)行代理對(duì)象業(yè)務(wù)方法
    orgService.saveOrg("軟件開發(fā)部");
    orgService.updateOrg(1);
    }
    }

    運(yùn)行結(jié)果:
    begin transaction
    save org 軟件開發(fā)部
    end transaction
    begin transaction
    update org which`s id is 1
    end transaction
    這樣我們便可以通過Proxy.newProxyInstance方法獲取不同類的代理對(duì)象,通過調(diào)用代理對(duì)象的相應(yīng)方法茶宵,底層會(huì)自動(dòng)幫我們完成擴(kuò)展業(yè)務(wù)類的invoke方法的調(diào)用危纫,并且返回結(jié)果。
    2.CGLIB動(dòng)態(tài)代理
    同樣的套路我們來測(cè)試CGLIB動(dòng)態(tài)代理乌庶。
    package proxy.part03;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
/**

  • 定義核心業(yè)務(wù)接口(或父類)
    /
    abstract class MessageService{
    abstract void sendMsg(String msg);
    }
    /
    *
  • 實(shí)現(xiàn)核心業(yè)務(wù)(或繼承父類)
    /
    class MessageServiceImpl extends MessageService{
    public void sendMsg(String msg){
    System.out.println(msg);
    }
    }
    /
    *
  • 定義管理事務(wù)的非核心業(yè)務(wù)類
    /
    class TxManager{
    //核心業(yè)務(wù)前執(zhí)行
    public void begin(){
    System.out.println("begin transaction");
    }
    //核心業(yè)務(wù)后執(zhí)行
    public void end(){
    System.out.println("end transaction");
    }
    }
    /
    *
  • 測(cè)試CGLIB動(dòng)態(tài)代理
    */
    public class TestCGLIBProxy01 {
    public static void main(String[] args) {
    //1.創(chuàng)建目標(biāo)對(duì)象
    MessageService msgService=new MessageServiceImpl();
    //2.創(chuàng)建代理對(duì)象
    //2.1創(chuàng)建一個(gè)代理增強(qiáng)器負(fù)責(zé)創(chuàng)建代理
    Enhancer enhancer=new Enhancer();
    //2.2設(shè)置增強(qiáng)器對(duì)應(yīng)的類加載器
    enhancer.setClassLoader(msgService.getClass().getClassLoader());
    //2.3設(shè)置代理對(duì)象要實(shí)現(xiàn)的接口或繼承的父類
    //當(dāng)目標(biāo)對(duì)象實(shí)現(xiàn)了接口就用這個(gè)方法
    enhancer.setInterfaces(msgService.getClass().getInterfaces());
    //當(dāng)目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口就用這個(gè)方法种蝶,這兩個(gè)方法可根據(jù)情況決定用哪一個(gè),也可以都是用
    enhancer.setSuperclass(msgService.getClass());
    //2.4指定回調(diào)函數(shù)
    enhancer.setCallback(new InvocationHandler() {
    //關(guān)聯(lián)非核心業(yè)務(wù)對(duì)象
    TxManager txManager=new TxManager();
    @Override
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
    txManager.begin();
    Object result=method.invoke(msgService, args);
    txManager.end();
    return result;
    }
    });
    //2.5創(chuàng)建代理對(duì)象
    MessageService proxy=(MessageService)enhancer.create();
    //3.執(zhí)行代理對(duì)象業(yè)務(wù)
    //在執(zhí)行業(yè)務(wù)方法時(shí)會(huì)回調(diào)Enhancer指定的callback接口中的函數(shù)
    proxy.sendMsg("hello");
    }
    }
    輸出結(jié)果:
    begin transaction
    hello
    end transaction
    這樣就實(shí)現(xiàn)了CGLIB動(dòng)態(tài)代理瞒大。

總結(jié):
代理模式是一種解決方案螃征,也是一項(xiàng)技術(shù),使用代理模式透敌,我們可以在不修改原代碼的情況下会傲,完成業(yè)務(wù)的拓展锅棕,并且保證了代碼的復(fù)用率和可維護(hù)性。
博主編寫的這兩種動(dòng)態(tài)代理的實(shí)現(xiàn)方式略有不同淌山,這也是博主故意為大家挖的坑裸燎,如果你仔細(xì)理解上面的兩個(gè)案例,會(huì)發(fā)現(xiàn)其實(shí)JDK動(dòng)態(tài)代理與CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)方式只有增強(qiáng)代理器對(duì)象(Enhancer)的setSuperclass方法不同泼疑,其他步驟邏輯上完全相同德绿,都需要被代理對(duì)象的類加載器、被代理對(duì)象實(shí)現(xiàn)的接口的Classd對(duì)象(或者所繼承的父類的Class對(duì)象退渗,也可以是本類的Class對(duì)象)移稳、一個(gè)處理擴(kuò)展業(yè)務(wù)的對(duì)象(擴(kuò)展業(yè)務(wù)代碼可以單獨(dú)寫到其他類中,也可以直接寫在InvocationHandler接口的實(shí)現(xiàn)類中)会油,交由API后个粱,我們就可以獲得代理對(duì)象。
這兩種動(dòng)態(tài)代理各有優(yōu)缺點(diǎn):
JDK動(dòng)態(tài)代理:優(yōu)點(diǎn)是翻翩,生成動(dòng)態(tài)代理對(duì)象的速度快都许,適合頻繁創(chuàng)建代理對(duì)象的程序;
缺點(diǎn)是嫂冻,執(zhí)行速度慢胶征,且不能為未實(shí)現(xiàn)接口的類創(chuàng)建代理。
CGLIB動(dòng)態(tài)代理:優(yōu)點(diǎn)是桨仿,執(zhí)行速度快睛低,且能夠?yàn)槲磳?shí)現(xiàn)接口的類創(chuàng)建代理;
缺點(diǎn)是服傍,不能代理final修飾的方法钱雷,且生成代理對(duì)象的速度慢,不適合頻繁創(chuàng)建代理對(duì)象的程序吹零。
在Spring中罩抗,默認(rèn)使用JDK動(dòng)態(tài)代理,如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口瘪校,會(huì)轉(zhuǎn)而使用CGLIB動(dòng)態(tài)代理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末名段,一起剝皮案震驚了整個(gè)濱河市阱扬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伸辟,老刑警劉巖麻惶,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異信夫,居然都是意外死亡窃蹋,警方通過查閱死者的電腦和手機(jī)卡啰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來警没,“玉大人匈辱,你說我怎么就攤上這事∩奔#” “怎么了亡脸?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)树酪。 經(jīng)常有香客問我浅碾,道長(zhǎng),這世上最難降的妖魔是什么续语? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任垂谢,我火速辦了婚禮,結(jié)果婚禮上疮茄,老公的妹妹穿的比我還像新娘滥朱。我一直安慰自己,他們只是感情好娃豹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布焚虱。 她就那樣靜靜地躺著,像睡著了一般懂版。 火紅的嫁衣襯著肌膚如雪鹃栽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天躯畴,我揣著相機(jī)與錄音民鼓,去河邊找鬼。 笑死蓬抄,一個(gè)胖子當(dāng)著我的面吹牛丰嘉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嚷缭,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼饮亏,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了阅爽?” 一聲冷哼從身側(cè)響起路幸,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎付翁,沒想到半個(gè)月后简肴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡百侧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年砰识,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了能扒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辫狼,死狀恐怖初斑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情予借,我是刑警寧澤越平,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站灵迫,受9級(jí)特大地震影響秦叛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瀑粥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一挣跋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧避咆,春花似錦、人聲如沸修噪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黄琼。三九已至樊销,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脏款,已是汗流浹背围苫。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撤师,地道東北人剂府。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剃盾,于是被迫代替她去往敵國和親腺占。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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