<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          給女同事講完代理后,女同事說(shuō):你好棒哦

          共 9465字,需瀏覽 19分鐘

           ·

          2020-10-17 05:29


          點(diǎn)擊藍(lán)色“Java建設(shè)者?”關(guān)注我喲

          加個(gè)“星標(biāo)”,及時(shí)閱讀最新技術(shù)文章




          這是Java建設(shè)者的第114篇原創(chuàng)文章


          說(shuō)在前面:今天我們來(lái)聊一聊 Java 中的代理,先來(lái)聊聊故事背景:

          ?

          小明想購(gòu)買(mǎi)法國(guó)某個(gè)牌子的香水送給女朋友,但是在國(guó)內(nèi)沒(méi)有貨源售賣(mài),親自去法國(guó)又大費(fèi)周章了,而小紅現(xiàn)在正在法國(guó)玩耍,她和小明是好朋友,可以幫小明買(mǎi)到這個(gè)牌子的香水,于是小明就找到小紅,答應(yīng)給她多加 5% 的辛苦費(fèi),小紅答應(yīng)了,小明成功在中國(guó)買(mǎi)到了法國(guó)的香水。之后小紅開(kāi)啟了瘋狂的代購(gòu)模式,賺到了很多手續(xù)費(fèi)。

          ?

          在故事中,「小明是一個(gè)客戶」,它讓小紅幫忙購(gòu)買(mǎi)香水,「小紅就成了一個(gè)代理對(duì)象」,而「香水提供商是一個(gè)真實(shí)的對(duì)象」,可以售賣(mài)香水,小明通過(guò)代理商小紅,購(gòu)買(mǎi)到法國(guó)的香水,這就是一個(gè)代購(gòu)的例子。我畫(huà)了一幅圖幫助理解這個(gè)故事的整個(gè)結(jié)構(gòu)。

          這個(gè)故事是最典型的代理模式,代購(gòu)從供應(yīng)商購(gòu)買(mǎi)貨物后返回給調(diào)用者,也就是需要代理的小明。

          代理可以分為靜態(tài)代理動(dòng)態(tài)代理兩大類(lèi):

          「靜態(tài)代理」

          • 優(yōu)點(diǎn):代碼結(jié)構(gòu)簡(jiǎn)單,較容易實(shí)現(xiàn)
          • 缺點(diǎn):無(wú)法適配所有代理場(chǎng)景,如果有新的需求,需要修改代理類(lèi),「不符合軟件工程的開(kāi)閉原則」

          小紅現(xiàn)在只是代理香水,如果小明需要找小紅買(mǎi)法國(guó)紅酒,那小紅就需要代理法國(guó)紅酒了,但是靜態(tài)代理去擴(kuò)展代理功能「必須修改小紅內(nèi)部的邏輯,這會(huì)讓小紅內(nèi)部代碼越來(lái)越臃腫」,后面會(huì)詳細(xì)分析。

          「動(dòng)態(tài)代理」

          • 優(yōu)點(diǎn):能夠動(dòng)態(tài)適配特定的代理場(chǎng)景,擴(kuò)展性較好,「符合軟件工程的開(kāi)閉原則」
          • 缺點(diǎn):動(dòng)態(tài)代理需要利用到反射機(jī)制和動(dòng)態(tài)生成字節(jié)碼,導(dǎo)致其性能會(huì)比靜態(tài)代理稍差一些,「但是相比于優(yōu)點(diǎn),這些劣勢(shì)幾乎可以忽略不計(jì)」

          如果小明需要找小紅代理紅酒,我們「無(wú)需修改代理類(lèi)小紅的內(nèi)部邏輯」,只需要關(guān)注擴(kuò)展的功能點(diǎn):「代理紅酒」,實(shí)例化新的類(lèi),通過(guò)一些轉(zhuǎn)換即可讓小紅「既能夠代理香水也能夠代理紅酒」了。

          本文將會(huì)通過(guò)以下幾點(diǎn),盡可能讓你理解 Java 代理中所有重要的知識(shí)點(diǎn):

          1. 學(xué)習(xí)代理模式(實(shí)現(xiàn)故事的代碼,解釋代理模式的類(lèi)結(jié)構(gòu)特點(diǎn))
          2. 比較靜態(tài)代理與動(dòng)態(tài)代理二者的異同
          3. Java 中常見(jiàn)的兩種動(dòng)態(tài)代理實(shí)現(xiàn)(JDK Proxy 和 Cglib)
          4. 動(dòng)態(tài)代理的應(yīng)用(Spring AOP)

          代理模式

          (1)我們定義好一個(gè)「售賣(mài)香水」的接口,定義好售賣(mài)香水的方法并傳入該香水的價(jià)格。

          public?interface?SellPerfume?{
          ????void?sellPerfume(double?price);
          }

          (2)定義香奈兒(Chanel)香水提供商,實(shí)現(xiàn)接口。

          public?class?ChanelFactory?implements?SellPerfume?{
          ????@Override
          ????public?void?sellPerfume(double?price)?{
          ????????System.out.println("成功購(gòu)買(mǎi)香奈兒品牌的香水,價(jià)格是:"?+?price?+?"元");
          ????}
          }

          (3)定義「小紅」代理類(lèi),她需要代購(gòu)去售賣(mài)香奈兒香水,所以她是香奈兒香水提供商的代理對(duì)象,同樣實(shí)現(xiàn)接口,并在內(nèi)部保存對(duì)目標(biāo)對(duì)象(香奈兒提供商)的引用,控制其它對(duì)象對(duì)目標(biāo)對(duì)象的訪問(wèn)。

          public?class?XiaoHongSellProxy?implements?SellPerfume?{
          ?private?SellPerfume?sellPerfumeFactory;
          ????public?XiaoHongSellProxy(SellPerfume?sellPerfumeFactory)?{
          ????????this.sellPerfumeFactory?=?sellPerfumeFactory;
          ????}
          ????@Override
          ????public?void?sellPerfume(double?price)?{
          ????????doSomethingBeforeSell();?//?前置增強(qiáng)
          ????????sellPerfumeFactory.sellPerfume(price);
          ????????doSomethingAfterSell();?//?后置增強(qiáng)
          ????}
          ????private?void?doSomethingBeforeSell()?{
          ????????System.out.println("小紅代理購(gòu)買(mǎi)香水前的額外操作...");
          ????}
          ????private?void?doSomethingAfterSell()?{
          ????????System.out.println("小紅代理購(gòu)買(mǎi)香水后的額外操作...");
          ????}
          }

          (4)小明是一個(gè)需求者,他需要去購(gòu)買(mǎi)香水,只能通過(guò)小紅去購(gòu)買(mǎi),所以他去找小紅購(gòu)買(mǎi)1999.99的香水。

          public?class?XiaoMing?{
          ????public?static?void?main(String[]?args)?{
          ????????ChanelFactory?factory?=?new?ChanelFactory();
          ????????XiaoHongSellProxy?proxy?=?new?XiaoHongSellProxy(factory);
          ????????proxy.sellPerfume(1999.99);
          ????}
          }

          我們來(lái)看看運(yùn)行結(jié)果,小紅在向小明售賣(mài)香水前可以執(zhí)行額外的其它操作,如果良心點(diǎn)的代購(gòu)就會(huì)「打折、包郵···」,如果黑心點(diǎn)的代購(gòu)就會(huì)「加手續(xù)費(fèi)、售出不退還···」,是不是很刺激。


          我們來(lái)看看上面 4 個(gè)類(lèi)組成的類(lèi)圖關(guān)系結(jié)構(gòu),可以發(fā)現(xiàn)「小紅」「香奈兒提供商」都實(shí)現(xiàn)了「售賣(mài)香水」這一接口,而小紅內(nèi)部增加了對(duì)提供商的引用,用于調(diào)用提供商的售賣(mài)香水功能。


          實(shí)現(xiàn)代理模式,需要走以下幾個(gè)步驟:

          • 「定義真實(shí)對(duì)象和代理對(duì)象的公共接口」(售賣(mài)香水接口)
          • 「代理對(duì)象內(nèi)部保存對(duì)真實(shí)目標(biāo)對(duì)象的引用」(小紅引用提供商)
          • 訪問(wèn)者僅能通過(guò)代理對(duì)象訪問(wèn)真實(shí)目標(biāo)對(duì)象,「不可直接訪問(wèn)目標(biāo)對(duì)象」(小明只能通過(guò)小紅去購(gòu)買(mǎi)香水,不能直接到香奈兒提供商購(gòu)買(mǎi))
          ?

          代理模式很容易產(chǎn)生錯(cuò)誤思維的一個(gè)地方:代理對(duì)象并不是真正提供服務(wù)的一個(gè)對(duì)象,它只是替訪問(wèn)者訪問(wèn)目標(biāo)對(duì)象的一個(gè)「中間人」,真正提供服務(wù)的還是目標(biāo)對(duì)象,而代理對(duì)象的作用就是在目標(biāo)對(duì)象提供服務(wù)之前和之后能夠執(zhí)行額外的邏輯。

          從故事來(lái)說(shuō),小紅并不是真正賣(mài)香水的,賣(mài)香水的還是香奈兒提供商,而小紅只不過(guò)是在讓香奈兒賣(mài)香水之前和之后執(zhí)行了一些自己額外加上去的操作。

          ?

          講完這個(gè)代理模式的代碼實(shí)現(xiàn),我們來(lái)系統(tǒng)地學(xué)習(xí)它究竟是如何定義的,以及實(shí)現(xiàn)它需要注意什么規(guī)范。

          代理模式的定義:「給目標(biāo)對(duì)象提供一個(gè)代理對(duì)象,代理對(duì)象包含該目標(biāo)對(duì)象,并控制對(duì)該目標(biāo)對(duì)象的訪問(wèn)?!?/strong>

          代理模式的目的:

          • 通過(guò)代理對(duì)象的隔離,可以在對(duì)目標(biāo)對(duì)象訪問(wèn)前后「增加額外的業(yè)務(wù)邏輯,實(shí)現(xiàn)功能增強(qiáng)?!?/strong>
          • 通過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象,可以「防止系統(tǒng)大量地直接對(duì)目標(biāo)對(duì)象進(jìn)行不正確地訪問(wèn)」,出現(xiàn)不可預(yù)測(cè)的后果

          靜態(tài)代理與動(dòng)態(tài)代理

          你是否會(huì)有我一樣的疑惑:代理為什么還要分靜態(tài)和動(dòng)態(tài)的?它們兩個(gè)有啥不同嗎?

          很明顯,所有人都會(huì)有這樣的疑惑,我們先來(lái)看看它們的相同點(diǎn):

          • 都能夠?qū)崿F(xiàn)代理模式(這不廢話嗎...)
          • 無(wú)論是靜態(tài)代理還是動(dòng)態(tài)代理,代理對(duì)象和目標(biāo)對(duì)象都需要實(shí)現(xiàn)一個(gè)「公共接口」

          重點(diǎn)當(dāng)然是它們的不同之處,動(dòng)態(tài)代理在靜態(tài)代理的基礎(chǔ)上做了改進(jìn),極大地提高了程序的「可維護(hù)性」「可擴(kuò)展性」。我先列出它們倆的不同之處,再詳細(xì)解釋為何靜態(tài)代理不具備這兩個(gè)特性:

          • 動(dòng)態(tài)代理產(chǎn)生代理對(duì)象的時(shí)機(jī)是「運(yùn)行時(shí)動(dòng)態(tài)生成」,它沒(méi)有 Java 源文件,「直接生成字節(jié)碼文件實(shí)例化代理對(duì)象」;而靜態(tài)代理的代理對(duì)象,在「程序編譯時(shí)」已經(jīng)寫(xiě)好 Java 文件了,直接 new 一個(gè)代理對(duì)象即可。
          • 動(dòng)態(tài)代理比靜態(tài)代理更加穩(wěn)健,對(duì)程序的可維護(hù)性和可擴(kuò)展性更加友好

          目前來(lái)看,代理對(duì)象小紅已經(jīng)能夠代理購(gòu)買(mǎi)香水了,但有一天,小紅的另外一個(gè)朋友小何來(lái)了,「他想購(gòu)買(mǎi)最純正的法國(guó)紅酒」,國(guó)內(nèi)沒(méi)有這樣的購(gòu)買(mǎi)渠道,小紅剛巧也在法國(guó),于是小何就想找小紅幫他買(mǎi)紅酒啦,這和小明找小紅是一個(gè)道理的,都是想讓小紅做代理。

          但問(wèn)題是:在程序中,小紅只能代理購(gòu)買(mǎi)香水,「如果要代理購(gòu)買(mǎi)紅酒」,要怎么做呢?

          • 創(chuàng)建售賣(mài)紅酒的接口

          • 售賣(mài)紅酒提供商和代理對(duì)象小紅都需要實(shí)現(xiàn)該接口

          • 小何訪問(wèn)小紅,讓小紅賣(mài)給他紅酒


          OK,事已至此,代碼就不重復(fù)寫(xiě)了,我們來(lái)探討一下,面對(duì)這種新增的場(chǎng)景,上面的這種實(shí)現(xiàn)方法有沒(méi)有什么缺陷呢?

          我們不得不提的是軟件工程中的「開(kāi)閉原則」

          ?

          開(kāi)閉原則:在編寫(xiě)程序的過(guò)程中,軟件的所有對(duì)象應(yīng)該是對(duì)擴(kuò)展是開(kāi)放的,而對(duì)修改是關(guān)閉的

          ?

          靜態(tài)代理違反了開(kāi)閉原則,原因是:面對(duì)新的需求時(shí),需要修改代理類(lèi),增加實(shí)現(xiàn)新的接口和方法,導(dǎo)致代理類(lèi)越來(lái)越龐大,變得難以維護(hù)。

          雖然說(shuō)目前代理類(lèi)只是實(shí)現(xiàn)了2個(gè)接口,**如果日后小紅不只是代理售賣(mài)紅酒,還需要代理售賣(mài)電影票、代購(gòu)日本壽司······**實(shí)現(xiàn)的接口會(huì)變得越來(lái)越多,內(nèi)部的結(jié)構(gòu)變得越來(lái)越復(fù)雜,「整個(gè)類(lèi)顯得愈發(fā)臃腫」,變得不可維護(hù),之后的擴(kuò)展也會(huì)成問(wèn)題,只要任意一個(gè)接口有改動(dòng),就會(huì)牽扯到這個(gè)代理類(lèi),維護(hù)的代價(jià)很高。

          「所以,為了提高類(lèi)的可擴(kuò)展性和可維護(hù)性,滿足開(kāi)閉原則,Java 提供了動(dòng)態(tài)代理機(jī)制?!?/strong>

          常見(jiàn)的動(dòng)態(tài)代理實(shí)現(xiàn)

          動(dòng)態(tài)代理最重要的當(dāng)然是「動(dòng)態(tài)」兩個(gè)字,學(xué)習(xí)動(dòng)態(tài)代理的過(guò)程,最重要的就是理解何為動(dòng)態(tài),話不多說(shuō),馬上開(kāi)整。

          我們來(lái)明確一點(diǎn):「動(dòng)態(tài)代理解決的問(wèn)題是面對(duì)新的需求時(shí),不需要修改代理對(duì)象的代碼,只需要新增接口和真實(shí)對(duì)象,在客戶端調(diào)用即可完成新的代理。」

          這樣做的目的:滿足軟件工程的開(kāi)閉原則,提高類(lèi)的可維護(hù)性和可擴(kuò)展性。

          JDK Proxy

          JDK Proxy 是 JDK 提供的一個(gè)動(dòng)態(tài)代理機(jī)制,它涉及到兩個(gè)核心類(lèi),分別是ProxyInvocationHandler,我們先來(lái)了解如何使用它們。

          以小紅代理賣(mài)香水的故事為例,香奈兒香水提供商依舊是真實(shí)對(duì)象,實(shí)現(xiàn)了SellPerfume接口,這里不再重新寫(xiě)了,重點(diǎn)是「小紅代理」,這里的代理對(duì)象不再是小紅一個(gè)人,而是一個(gè)「代理工廠」,里面會(huì)有許多的代理對(duì)象。我畫(huà)了一幅圖,你看了之后會(huì)很好理解:


          小明來(lái)到代理工廠,需要購(gòu)買(mǎi)一款法國(guó)在售的香奈兒香水,那么工廠就會(huì)**找一個(gè)可以實(shí)際的代理對(duì)象(動(dòng)態(tài)實(shí)例化)**分配給小明,例如小紅或者小花,讓該代理對(duì)象完成小明的需求。「該代理工廠含有無(wú)窮無(wú)盡的代理對(duì)象可以分配,且每個(gè)對(duì)象可以代理的事情可以根據(jù)程序的變化而動(dòng)態(tài)變化,無(wú)需修改代理工廠。」

          如果有一天小明需要招待一個(gè)可以「代購(gòu)紅酒」的代理對(duì)象,該代理工廠依舊可以滿足他的需求,無(wú)論日后需要什么代理,都可以滿足,是不是覺(jué)得很神奇?我們來(lái)學(xué)習(xí)如何使用它。

          我們看一下動(dòng)態(tài)代理的 UML 類(lèi)圖結(jié)構(gòu)長(zhǎng)什么樣子。


          可以看到和靜態(tài)代理區(qū)別不大,唯一的變動(dòng)是代理對(duì)象,我做了標(biāo)注:「由代理工廠生產(chǎn)」。

          這句話的意思是:「代理對(duì)象是在程序運(yùn)行過(guò)程中,由代理工廠動(dòng)態(tài)生成,代理對(duì)象本身不存在 Java 源文件」。

          那么,我們的關(guān)注點(diǎn)有2個(gè):

          • 如何實(shí)現(xiàn)一個(gè)代理工廠
          • 如何通過(guò)代理工廠動(dòng)態(tài)生成代理對(duì)象

          首先,代理工廠需要實(shí)現(xiàn)InvocationHanlder接口并實(shí)現(xiàn)其invoke()方法。

          public?class?SellProxyFactory?implements?InvocationHandler?{
          ?/**?代理的真實(shí)對(duì)象?*/
          ????private?Object?realObject;

          ????public?SellProxyFactory(Object?realObject)?{
          ????????this.realObject?=?realObject;
          ????}

          ????@Override
          ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ????????doSomethingBefore();
          ????????Object?obj?=?method.invoke(realObject,?args);
          ????????doSomethingAfter();
          ????????return?obj;
          ????}

          ????private?void?doSomethingAfter()?{
          ????????System.out.println("執(zhí)行代理后的額外操作...");
          ????}

          ????private?void?doSomethingBefore()?{
          ????????System.out.println("執(zhí)行代理前的額外操作...");
          ????}
          ????
          }

          invoke() 方法有3個(gè)參數(shù):

          • Object proxy:代理對(duì)象
          • Method method:真正執(zhí)行的方法
          • Object[] agrs:調(diào)用第二個(gè)參數(shù) method 時(shí)傳入的參數(shù)列表值

          invoke() 方法是一個(gè)代理方法,也就是說(shuō)最后客戶端請(qǐng)求代理時(shí),執(zhí)行的就是該方法。代理工廠類(lèi)到這里為止已經(jīng)結(jié)束了,我們接下來(lái)看第二點(diǎn):「如何通過(guò)代理工廠動(dòng)態(tài)生成代理對(duì)象」。

          生成代理對(duì)象需要用到Proxy類(lèi),它可以幫助我們生成任意一個(gè)代理對(duì)象,里面提供一個(gè)靜態(tài)方法newProxyInstance。

          ?Proxy.newProxyInstance(ClassLoader?loader,?Class[]?interfaces,?InvocationHandler?h);

          實(shí)例化代理對(duì)象時(shí),需要傳入3個(gè)參數(shù):

          • ClassLoader loader:加載動(dòng)態(tài)代理類(lèi)的類(lèi)加載器
          • Class[] interfaces:代理類(lèi)實(shí)現(xiàn)的接口,可以傳入多個(gè)接口
          • InvocationHandler h:指定代理類(lèi)的「調(diào)用處理程序」,即調(diào)用接口中的方法時(shí),會(huì)找到該代理工廠h,執(zhí)行invoke()方法

          我們?cè)诳蛻舳苏?qǐng)求代理時(shí),就需要用到上面這個(gè)方法。

          public?class?XiaoMing?{
          ????public?static?void?main(String[]?args)?{
          ????????ChanelFactory?chanelFactory?=?new?ChanelFactory();
          ????????SellProxyFactory?sellProxyFactory?=?new?SellProxyFactory(chanelFactory);
          ????????SellPerfume?sellPerfume?=?(SellPerfume)?Proxy.newProxyInstance(chanelFactory.getClass().getClassLoader(),
          ????????????????chanelFactory.getClass().getInterfaces(),
          ????????????????sellProxyFactory);
          ????????sellPerfume.sellPerfume(1999.99);
          ????}
          }

          執(zhí)行結(jié)果和靜態(tài)代理的結(jié)果相同,但二者的思想是不一樣的,一個(gè)是靜態(tài),一個(gè)是動(dòng)態(tài)。那又如何體現(xiàn)出動(dòng)態(tài)代理的優(yōu)勢(shì)呢?別急,往下看就知道了。

          ?

          注意看下圖,相比靜態(tài)代理的前置增強(qiáng)和后置增強(qiáng),少了「小紅」二字,實(shí)際上代理工廠分配的代理對(duì)象是隨機(jī)的,不會(huì)針對(duì)某一個(gè)具體的代理對(duì)象,所以每次生成的代理對(duì)象都不一樣,也就不確定是不是小紅了,但是能夠唯一確定的是,「這個(gè)代理對(duì)象能和小紅一樣幫小明買(mǎi)到香水!」

          ?


          按照之前的故事線發(fā)展,小紅去代理紅酒,而「小明又想買(mǎi)法國(guó)的名牌紅酒」,所以去找代理工廠,讓它再分配一個(gè)人幫小明買(mǎi)紅酒,代理工廠說(shuō):“當(dāng)然沒(méi)問(wèn)題!我們是專(zhuān)業(yè)的!等著!”

          我們需要實(shí)現(xiàn)兩個(gè)類(lèi):紅酒提供商類(lèi) 和 售賣(mài)紅酒接口。

          /**?售賣(mài)紅酒接口?*/
          public?interface?SellWine?{
          ????void?sellWine(double?price);
          }

          /**?紅酒供應(yīng)商?*/
          public?class?RedWineFactory?implements?SellWine?{

          ????@Override
          ????public?void?sellWine(double?price)?{
          ????????System.out.println("成功售賣(mài)一瓶紅酒,價(jià)格:"?+?price?+?"元");????
          ????}

          }

          然后我們的小明在請(qǐng)求代理工廠時(shí),就可以「實(shí)例化一個(gè)可以售賣(mài)紅酒的代理」了。

          public?class?XiaoMing?{
          ????public?static?void?main(String[]?args)?{
          ????????//?實(shí)例化一個(gè)紅酒銷(xiāo)售商
          ????????RedWineFactory?redWineFactory?=?new?RedWineFactory();
          ????????//?實(shí)例化代理工廠,傳入紅酒銷(xiāo)售商引用控制對(duì)其的訪問(wèn)
          ????????SellProxyFactory?sellProxyFactory?=?new?SellProxyFactory(redWineFactory);
          ????????//?實(shí)例化代理對(duì)象,該對(duì)象可以代理售賣(mài)紅酒
          ????????SellWine?sellWineProxy?=?(SellWine)?Proxy.newProxyInstance(redWineFactory.getClass().getClassLoader(),
          ????????????????redWineFactory.getClass().getInterfaces(),
          ????????????????sellProxyFactory);
          ????????//?代理售賣(mài)紅酒
          ????????sellWineProxy.sellWine(1999.99);
          ????}
          }

          期待一下執(zhí)行結(jié)果,你會(huì)很驚喜地發(fā)現(xiàn),居然也能夠代理售賣(mài)紅酒了,但是我們「沒(méi)有修改代理工廠」。


          回顧一下我們新增紅酒代理功能時(shí),需要2個(gè)步驟:

          • 創(chuàng)建新的紅酒提供商SellWineFactory和售賣(mài)紅酒接口SellWine
          • 在客戶端實(shí)例化一個(gè)代理對(duì)象,然后向該代理對(duì)象購(gòu)買(mǎi)紅酒

          再回想「開(kāi)閉原則:面向擴(kuò)展開(kāi)放,面向修改關(guān)閉」。動(dòng)態(tài)代理正是滿足了這一重要原則,在面對(duì)功能需求擴(kuò)展時(shí),只需要關(guān)注擴(kuò)展的部分,不需要修改系統(tǒng)中原有的代碼。

          如果感興趣想深究的朋友,把注意力放在Proxy.newProxyInstance()這個(gè)方法上,這是整個(gè) JDK 動(dòng)態(tài)代理起飛的一個(gè)方法。

          講到這里,JDK 提供的動(dòng)態(tài)代理已經(jīng)到尾聲了,我們來(lái)總結(jié)一下 JDK 的動(dòng)態(tài)代理:

          (1)JDK 動(dòng)態(tài)代理的使用方法

          • 代理工廠需要實(shí)現(xiàn) InvocationHandler接口,調(diào)用代理方法時(shí)會(huì)轉(zhuǎn)向執(zhí)行invoke()方法
          • 生成代理對(duì)象需要使用Proxy對(duì)象中的newProxyInstance()方法,返回對(duì)象可強(qiáng)轉(zhuǎn)成傳入的其中一個(gè)接口,然后調(diào)用接口方法即可實(shí)現(xiàn)代理

          (2)JDK 動(dòng)態(tài)代理的特點(diǎn)

          • 目標(biāo)對(duì)象強(qiáng)制需要實(shí)現(xiàn)一個(gè)接口,否則無(wú)法使用 JDK 動(dòng)態(tài)代理

          「(以下為擴(kuò)展內(nèi)容,如果不想看可跳過(guò))」

          Proxy.newProxyInstance() 是生成動(dòng)態(tài)代理對(duì)象的關(guān)鍵,我們可來(lái)看看它里面到底干了些什么,我把重要的代碼提取出來(lái),一些對(duì)分析無(wú)用的代碼就省略掉了。

          private?static?final?Class[]?constructorParams?={?InvocationHandler.class?};
          public?static?Object?newProxyInstance(ClassLoader?loader,
          ??????????????????????????????????????????Class[]?interfaces,
          ??????????????????????????????????????????InvocationHandler?h)
          ?
          {
          ????//?獲取代理類(lèi)的?Class?對(duì)象
          ????Class?cl?=?getProxyClass0(loader,?intfs);
          ????//?獲取代理對(duì)象的顯示構(gòu)造器,參數(shù)類(lèi)型是?InvocationHandler
          ????final?Constructor?cons?=?cl.getConstructor(constructorParams);
          ????//?反射,通過(guò)構(gòu)造器實(shí)例化動(dòng)態(tài)代理對(duì)象
          ????return?cons.newInstance(new?Object[]{h});
          }

          我們看到第 6 行獲取了一個(gè)動(dòng)態(tài)代理對(duì)象,那么是如何生成的呢?接著往下看。

          private?static?Class?getProxyClass0(ClassLoader?loader,
          ???????????????????????????????????????Class...?interfaces)?{
          ????//?去代理類(lèi)對(duì)象緩存中獲取代理類(lèi)的?Class?對(duì)象
          ????return?proxyClassCache.get(loader,?interfaces);
          }

          發(fā)現(xiàn)里面用到一個(gè)緩存 「proxyClassCache」,從結(jié)構(gòu)來(lái)看類(lèi)似于是一個(gè) map 結(jié)構(gòu),根據(jù)類(lèi)加載器loader和真實(shí)對(duì)象實(shí)現(xiàn)的接口interfaces查找是否有對(duì)應(yīng)的 Class 對(duì)象,我們接著往下看 get() 方法。

          ?public?V?get(K?key,?P?parameter)?{
          ?????//?先從緩存中查詢是否能根據(jù)?key?和?parameter?查詢到?Class?對(duì)象
          ?????//?...
          ?????//?生成一個(gè)代理類(lèi)
          ?????Object?subKey?=?Objects.requireNonNull(subKeyFactory.apply(key,?parameter));
          ?}

          在 get() 方法中,如果沒(méi)有從緩存中獲取到 Class 對(duì)象,則需要利用 「subKeyFactory」 去實(shí)例化一個(gè)動(dòng)態(tài)代理對(duì)象,而在 「Proxy」 類(lèi)中包含一個(gè) 「ProxyClassFactory」 內(nèi)部類(lèi),由它來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)代理類(lèi),所以我們接著去看 ProxyClassFactory 中的 apply() 方法。

          private?static?final?class?ProxyClassFactory
          ????implements?BiFunction<ClassLoader,?Class[],?Class>?
          {
          ????//?非常重要,這就是我們看到的動(dòng)態(tài)代理的對(duì)象名前綴!
          ?private?static?final?String?proxyClassNamePrefix?=?"$Proxy";

          ????@Override
          ????public?Class?apply(ClassLoader?loader,?Class[]?interfaces)?{
          ????????Map,?Boolean>?interfaceSet?=?new?IdentityHashMap<>(interfaces.length);
          ????????//?一些狀態(tài)校驗(yàn)
          ??
          ????????//?計(jì)數(shù)器,該計(jì)數(shù)器記錄了當(dāng)前已經(jīng)實(shí)例化多少個(gè)代理對(duì)象
          ????????long?num?=?nextUniqueNumber.getAndIncrement();
          ????????//?動(dòng)態(tài)代理對(duì)象名拼接!包名?+?"$Proxy"?+?數(shù)字
          ????????String?proxyName?=?proxyPkg?+?proxyClassNamePrefix?+?num;

          ????????//?生成字節(jié)碼文件,返回一個(gè)字節(jié)數(shù)組
          ????????byte[]?proxyClassFile?=?ProxyGenerator.generateProxyClass(
          ????????????proxyName,?interfaces,?accessFlags);
          ????????try?{
          ????????????//?利用字節(jié)碼文件創(chuàng)建該字節(jié)碼的?Class?類(lèi)對(duì)象
          ????????????return?defineClass0(loader,?proxyName,
          ????????????????????????????????proxyClassFile,?0,?proxyClassFile.length);
          ????????}?catch?(ClassFormatError?e)?{
          ????????????throw?new?IllegalArgumentException(e.toString());
          ????????}
          ????}
          }

          apply() 方法中注意有「兩個(gè)非常重要的方法」

          • 「ProxyGenerator.generateProxyClass()」:它是生成字節(jié)碼文件的方法,它返回了一個(gè)字節(jié)數(shù)組,字節(jié)碼文件本質(zhì)上就是一個(gè)字節(jié)數(shù)組,所以 proxyClassFile數(shù)組就是一個(gè)字節(jié)碼文件
          • 「defineClass0()」:生成字節(jié)碼文件的 Class 對(duì)象,它是一個(gè) native 本地方法,調(diào)用操作系統(tǒng)底層的方法創(chuàng)建類(lèi)對(duì)象

          proxyName 是代理對(duì)象的名字,我們可以看到它利用了 「proxyClassNamePrefix + 計(jì)數(shù)器」 拼接成一個(gè)新的名字。所以在 DEBUG 時(shí),停留在代理對(duì)象變量上,你會(huì)發(fā)現(xiàn)變量名是$Proxy0


          到了這里,源碼分析完了,是不是感覺(jué)被掏空了?哈哈哈哈,其實(shí)我當(dāng)時(shí)也有這種感覺(jué),不過(guò)現(xiàn)在你也感覺(jué)到,JDK 的動(dòng)態(tài)代理其實(shí)并不是特別復(fù)雜吧(只要你有毅力)

          CGLIB

          CGLIB(Code generation Library) 不是 JDK 自帶的動(dòng)態(tài)代理,它需要導(dǎo)入第三方依賴(lài),它是一個(gè)字節(jié)碼生成類(lèi)庫(kù),能夠在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi)對(duì) 「Java類(lèi) 和 Java接口」 擴(kuò)展。

          CGLIB不僅能夠?yàn)?Java接口 做代理,而且「能夠?yàn)槠胀ǖ?Java類(lèi) 做代理」,而 JDK Proxy 「只能為實(shí)現(xiàn)了接口」的 Java類(lèi) 做代理,所以 CGLIB 為 Java 的代理做了很好的擴(kuò)展。「如果需要代理的類(lèi)沒(méi)有實(shí)現(xiàn)接口,可以選擇 Cglib 作為實(shí)現(xiàn)動(dòng)態(tài)代理的工具?!?/strong>

          廢話太多,一句話概括:「CGLIB 可以代理沒(méi)有實(shí)現(xiàn)接口的 Java 類(lèi)」

          下面我們來(lái)學(xué)習(xí)它的使用方法,以「小明找代理工廠買(mǎi)法國(guó)香水」這個(gè)故事背景為例子。

          (1)導(dǎo)入依賴(lài)


          ????cglib
          ????cglib-nodep
          ????3.3.0
          ????test

          ?

          還有另外一個(gè) CGLIB 包,二者的區(qū)別是帶有-nodep的依賴(lài)內(nèi)部已經(jīng)包括了ASM字節(jié)碼框架的相關(guān)代碼,無(wú)需額外依賴(lài)ASM

          ?

          (2)CGLIB 代理中有兩個(gè)核心的類(lèi):MethodInterceptor接口 和 Enhancer類(lèi),前者是實(shí)現(xiàn)一個(gè)代理工廠的根接口,后者是創(chuàng)建動(dòng)態(tài)代理對(duì)象的類(lèi),在這里我再貼一次故事的結(jié)構(gòu)圖,幫助你們理解。


          首先我們來(lái)定義代理工廠SellProxyFactory。

          public?class?SellProxyFactory?implements?MethodInterceptor?{
          ????//?關(guān)聯(lián)真實(shí)對(duì)象,控制對(duì)真實(shí)對(duì)象的訪問(wèn)
          ????private?Object?realObject;
          ????/**?從代理工廠中獲取一個(gè)代理對(duì)象實(shí)例,等價(jià)于創(chuàng)建小紅代理?*/
          ????public?Object?getProxyInstance(Object?realObject)?{
          ????????this.realObject?=?realObject;
          ????????Enhancer?enhancer?=?new?Enhancer();
          ????????//?設(shè)置需要增強(qiáng)類(lèi)的類(lèi)加載器
          ????????enhancer.setClassLoader(realObject.getClass().getClassLoader());
          ????????//?設(shè)置被代理類(lèi),真實(shí)對(duì)象
          ????????enhancer.setSuperclass(realObject.getClass());
          ????????//?設(shè)置方法攔截器,代理工廠
          ????????enhancer.setCallback(this);
          ????????//?創(chuàng)建代理類(lèi)
          ????????return?enhancer.create();
          ????}
          ????
          ????@Override
          ????public?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{
          ????????doSomethingBefore();?//?前置增強(qiáng)
          ????????Object?object?=?methodProxy.invokeSuper(o,?objects);
          ????????doSomethingAfter();?//?后置增強(qiáng)
          ????????return?object;
          ????}

          ????private?void?doSomethingBefore()?{
          ????????System.out.println("執(zhí)行方法前額外的操作...");
          ????}

          ????private?void?doSomethingAfter()?{
          ????????System.out.println("執(zhí)行方法后額外的操作...");
          ????}

          }

          intercept() 方法涉及到 4 個(gè)參數(shù):

          • Object o:被代理對(duì)象
          • Method method:被攔截的方法
          • Object[] objects:被攔截方法的所有入?yún)⒅?/span>
          • MethodProxy methodProxy:方法代理,用于調(diào)用原始的方法
          ?

          對(duì)于 methodProxy 參數(shù)調(diào)用的方法,在其內(nèi)部有兩種選擇:invoke()invokeSuper() ,二者的區(qū)別不在本文展開(kāi)說(shuō)明,感興趣的讀者可以參考本篇文章:Cglib源碼分析 invoke和invokeSuper的差別

          ?

          getInstance() 方法中,利用 Enhancer 類(lèi)實(shí)例化代理對(duì)象(可以看作是小紅)返回給調(diào)用者小明,即可完成代理操作。

          public?class?XiaoMing?{
          ????public?static?void?main(String[]?args)?{
          ????????SellProxyFactory?sellProxyFactory?=?new?SellProxyFactory();
          ????????//?獲取一個(gè)代理實(shí)例
          ????????SellPerfumeFactory?proxyInstance?=
          ????????????????(SellPerfumeFactory)?sellProxyFactory.getProxyInstance(new?SellPerfumeFactory());
          ????????//?創(chuàng)建代理類(lèi)
          ????????proxyInstance.sellPerfume(1999.99);
          ????}
          }

          我們關(guān)注點(diǎn)依舊放在可擴(kuò)展性和可維護(hù)性上,Cglib 依舊符合「開(kāi)閉原則」,如果小明需要小紅代理購(gòu)買(mǎi)紅酒,該如何做呢?這里礙于篇幅原因,我不再將完整的代碼貼出來(lái)了,可以自己試著手動(dòng)實(shí)現(xiàn)一下,或者在心里有一個(gè)大概的實(shí)現(xiàn)思路即可。

          我們來(lái)總結(jié)一下 CGLIB 動(dòng)態(tài)代理:

          (1)CGLIB 的使用方法:

          • 代理工廠需要「實(shí)現(xiàn) MethodInterceptor 接口」,并重寫(xiě)方法,「內(nèi)部關(guān)聯(lián)真實(shí)對(duì)象」,控制第三者對(duì)真實(shí)對(duì)象的訪問(wèn);代理工廠內(nèi)部暴露 getInstance(Object realObject) 方法,「用于從代理工廠中獲取一個(gè)代理對(duì)象實(shí)例」。
          • Enhancer 類(lèi)用于從代理工廠中實(shí)例化一個(gè)代理對(duì)象,給調(diào)用者提供代理服務(wù)。

          JDK Proxy 和 CGLIB 的對(duì)比

          (2)仔細(xì)對(duì)比一下,JDK Proxy 和 CGLIB 具有相似之處:


          JDK ProxyCGLIB
          代理工廠實(shí)現(xiàn)接口InvocationHandlerMethodInterceptor
          構(gòu)造代理對(duì)象給 Client 服務(wù)ProxyEnhancer

          二者都是用到了兩個(gè)核心的類(lèi),它們也有不同:

          • 最明顯的不同:CGLIB 可以代理「大部分類(lèi)」(第二點(diǎn)說(shuō)到);而 JDK Proxy 「僅能夠代理實(shí)現(xiàn)了接口的類(lèi)」

          • CGLIB 采用動(dòng)態(tài)創(chuàng)建被代理類(lèi)的子類(lèi)實(shí)現(xiàn)方法攔截,子類(lèi)內(nèi)部重寫(xiě)被攔截的方法,所以 CGLIB 不能代理被 final 關(guān)鍵字修飾的類(lèi)和方法

          ?

          細(xì)心的讀者會(huì)發(fā)現(xiàn),講的東西都是「淺嘗輒止」(你都沒(méi)有給我講源碼,水文實(shí)錘),動(dòng)態(tài)代理的精髓在于「程序在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi)對(duì)象,攔截調(diào)用方法,在調(diào)用方法前后擴(kuò)展額外的功能」,而生成動(dòng)態(tài)代理對(duì)象的原理就是「反射機(jī)制」,在上一篇文章中,我詳細(xì)講到了如何利用反射實(shí)例化對(duì)象,調(diào)用方法······在代理中運(yùn)用得淋漓盡致,所以反射和代理也是天生的一對(duì),談到其中一個(gè),必然會(huì)涉及另外一個(gè)。

          ?

          動(dòng)態(tài)代理的實(shí)際應(yīng)用

          傳統(tǒng)的 OOP 編程符合從上往下的編碼關(guān)系,卻不符合從左往右的編碼關(guān)系,如果你看不懂,可以參考下面的動(dòng)圖,OOP 滿足我們一個(gè)方法一個(gè)方法從上往下地執(zhí)行,但是卻不能「從左往右嵌入代碼」,而 AOP 的出現(xiàn)很好地彌補(bǔ)了這一點(diǎn),它「允許我們將重復(fù)的代碼邏輯抽取出來(lái)形成一個(gè)單獨(dú)的覆蓋層」,在執(zhí)行代碼時(shí)可以將該覆蓋層毫無(wú)知覺(jué)的嵌入到原代碼邏輯里面去。

          Spring AOP

          如下圖所示,method1 和 method2 都需要在方法執(zhí)行前后「記錄日志」,實(shí)際上會(huì)有更多的方法需要記錄日志,傳統(tǒng)的 OOP 只能夠讓我們?cè)诿總€(gè)方法前后手動(dòng)記錄日志,大量的Log.info存在于方法內(nèi)部,導(dǎo)致代碼閱讀性下降,方法內(nèi)部無(wú)法專(zhuān)注于自己的邏輯。

          「AOP 可以將這些重復(fù)性的代碼包裝到額外的一層,監(jiān)聽(tīng)方法的執(zhí)行,當(dāng)方法被調(diào)用時(shí),通用的日志記錄層會(huì)攔截掉該方法,在該方法調(diào)用前后記錄日志,這樣可以讓方法專(zhuān)注于自己的業(yè)務(wù)邏輯而無(wú)需關(guān)注其它不必要的信息。」

          2

          Spring AOP 有許多功能:提供緩存、提供日志環(huán)繞、事務(wù)處理······在這里,我會(huì)以「事務(wù)」作為例子向你講解 Spring 底層是如何使用動(dòng)態(tài)代理的。

          Spring 的事務(wù)涉及到一個(gè)核心注解@Transactional,相信很多人在項(xiàng)目中都用到過(guò),加上這個(gè)注解之后,在執(zhí)行方法時(shí)如果發(fā)生異常,該方法內(nèi)所有的事務(wù)都回滾,否則全部提交生效,這是最宏觀的表現(xiàn),它內(nèi)部是如何實(shí)現(xiàn)的呢?今天就來(lái)簡(jiǎn)單分析一下。

          每個(gè)有關(guān)數(shù)據(jù)庫(kù)的操作都要保證一個(gè)事務(wù)內(nèi)的所有操作,要么全部執(zhí)行成功,要么全部執(zhí)行失敗,傳統(tǒng)的事務(wù)失敗回滾和成功提交是使用try...catch代碼塊完成的

          SqlSession?session?=?null;
          try{
          ????session?=?getSqlSessionFactory().openSession(false);
          ????session.update("...",?new?Object());
          ????//?事務(wù)提交
          ????session.commit();
          }catch(Exception?e){
          ????//?事務(wù)回滾
          ????session.rollback();
          ????throw?e;
          }finally{
          ????//?關(guān)閉事務(wù)
          ????session.close();
          }

          如果多個(gè)方法都需要寫(xiě)這一段邏輯非常冗余,所以 Spring 給我們封裝了一個(gè)注解 @Transactional,使用它后,調(diào)用方法時(shí)會(huì)監(jiān)視方法,如果方法上含有該注解,就會(huì)自動(dòng)幫我們把數(shù)據(jù)庫(kù)相關(guān)操作的代碼包裹起來(lái),最終形成類(lèi)似于上面的一段代碼原理,當(dāng)然這里并不準(zhǔn)確,只是給你們一個(gè)大概的總覽,了解Spring AOP 的本質(zhì)在干什么,這篇文章講解到這里,知識(shí)量應(yīng)該也非常多了,好好消化上面的知識(shí)點(diǎn),為后面的 Spring AOP 專(zhuān)題學(xué)習(xí)打下堅(jiān)實(shí)的基礎(chǔ)。

          END


          瀏覽 72
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    青青操国产手机在线视频 | 变态重口变态级无码AV | 豆花AV在线入口 | AExXxX国产 | 在线观看av网站 中文字幕久久久久 |