自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

 [[338991]]

說在前面:今天我們來聊一聊 Java 中的代理,先來聊聊故事背景:

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

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

 

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

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

「靜態(tài)代理」

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

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

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

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

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

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

學(xué)習(xí)代理模式(實(shí)現(xiàn)故事的代碼,解釋代理模式的類結(jié)構(gòu)特點(diǎn))

比較靜態(tài)代理與動(dòng)態(tài)代理二者的異同

Java 中常見的兩種動(dòng)態(tài)代理實(shí)現(xiàn)(JDK Proxy 和 Cglib)

動(dòng)態(tài)代理的應(yīng)用(Spring AOP)

代理模式

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

  1. public interface SellPerfume { 
  2.     void sellPerfume(double price); 

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

  1. public class ChanelFactory implements SellPerfume { 
  2.     @Override 
  3.     public void sellPerfume(double price) { 
  4.         System.out.println("成功購買香奈兒品牌的香水,價(jià)格是:" + price + "元"); 
  5.     } 

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

  1. public class XiaoHongSellProxy implements SellPerfume { 
  2.  private SellPerfume sellPerfumeFactory; 
  3.     public XiaoHongSellProxy(SellPerfume sellPerfumeFactory) { 
  4.         this.sellPerfumeFactory = sellPerfumeFactory; 
  5.     } 
  6.     @Override 
  7.     public void sellPerfume(double price) { 
  8.         doSomethingBeforeSell(); // 前置增強(qiáng) 
  9.         sellPerfumeFactory.sellPerfume(price); 
  10.         doSomethingAfterSell(); // 后置增強(qiáng) 
  11.     } 
  12.     private void doSomethingBeforeSell() { 
  13.         System.out.println("小紅代理購買香水前的額外操作..."); 
  14.     } 
  15.     private void doSomethingAfterSell() { 
  16.         System.out.println("小紅代理購買香水后的額外操作..."); 
  17.     } 

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

  1. public class XiaoMing { 
  2.     public static void main(String[] args) { 
  3.         ChanelFactory factory = new ChanelFactory(); 
  4.         XiaoHongSellProxy proxy = new XiaoHongSellProxy(factory); 
  5.         proxy.sellPerfume(1999.99); 
  6.     } 

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

 

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

 

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

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

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

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

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

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

代理模式的目的:

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

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

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

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

  • 都能夠?qū)崿F(xiàn)代理模式(這不廢話嗎...)
  • 無論是靜態(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)生成」,它沒有 Java 源文件,「直接生成字節(jié)碼文件實(shí)例化代理對(duì)象」;而靜態(tài)代理的代理對(duì)象,在「程序編譯時(shí)」已經(jīng)寫好 Java 文件了,直接 new 一個(gè)代理對(duì)象即可。
  • 動(dòng)態(tài)代理比靜態(tài)代理更加穩(wěn)健,對(duì)程序的可維護(hù)性和可擴(kuò)展性更加友好

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

但問題是:在程序中,小紅只能代理購買香水,「如果要代理購買紅酒」,要怎么做呢?

  • 創(chuàng)建售賣紅酒的接口
  • 售賣紅酒提供商和代理對(duì)象小紅都需要實(shí)現(xiàn)該接口
  • 小何訪問小紅,讓小紅賣給他紅酒

 

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

我們不得不提的是軟件工程中的「開閉原則」

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

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

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

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

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

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

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

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

JDK Proxy

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

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

 

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

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

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

 

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

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

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

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

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

  1. public class SellProxyFactory implements InvocationHandler { 
  2.  /** 代理的真實(shí)對(duì)象 */ 
  3.     private Object realObject; 
  4.  
  5.     public SellProxyFactory(Object realObject) { 
  6.         this.realObject = realObject; 
  7.     } 
  8.  
  9.     @Override 
  10.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  11.         doSomethingBefore(); 
  12.         Object obj = method.invoke(realObject, args); 
  13.         doSomethingAfter(); 
  14.         return obj; 
  15.     } 
  16.  
  17.     private void doSomethingAfter() { 
  18.         System.out.println("執(zhí)行代理后的額外操作..."); 
  19.     } 
  20.  
  21.     private void doSomethingBefore() { 
  22.         System.out.println("執(zhí)行代理前的額外操作..."); 
  23.     } 
  24.      

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

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

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

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

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

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

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

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

  1. public class XiaoMing { 
  2.     public static void main(String[] args) { 
  3.         ChanelFactory chanelFactory = new ChanelFactory(); 
  4.         SellProxyFactory sellProxyFactory = new SellProxyFactory(chanelFactory); 
  5.         SellPerfume sellPerfume = (SellPerfume) Proxy.newProxyInstance(chanelFactory.getClass().getClassLoader(), 
  6.                 chanelFactory.getClass().getInterfaces(), 
  7.                 sellProxyFactory); 
  8.         sellPerfume.sellPerfume(1999.99); 
  9.     } 

執(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ì)象能和小紅一樣幫小明買到香水!」❞


 

 

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

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

  1. /** 售賣紅酒接口 */ 
  2. public interface SellWine { 
  3.     void sellWine(double price); 
  4.  
  5. /** 紅酒供應(yīng)商 */ 
  6. public class RedWineFactory implements SellWine { 
  7.  
  8.     @Override 
  9.     public void sellWine(double price) { 
  10.         System.out.println("成功售賣一瓶紅酒,價(jià)格:" + price + "元");     
  11.     } 
  12.  

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

  1. public class XiaoMing { 
  2.     public static void main(String[] args) { 
  3.         // 實(shí)例化一個(gè)紅酒銷售商 
  4.         RedWineFactory redWineFactory = new RedWineFactory(); 
  5.         // 實(shí)例化代理工廠,傳入紅酒銷售商引用控制對(duì)其的訪問 
  6.         SellProxyFactory sellProxyFactory = new SellProxyFactory(redWineFactory); 
  7.         // 實(shí)例化代理對(duì)象,該對(duì)象可以代理售賣紅酒 
  8.         SellWine sellWineProxy = (SellWine) Proxy.newProxyInstance(redWineFactory.getClass().getClassLoader(), 
  9.                 redWineFactory.getClass().getInterfaces(), 
  10.                 sellProxyFactory); 
  11.         // 代理售賣紅酒 
  12.         sellWineProxy.sellWine(1999.99); 
  13.     } 

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

 

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

  • 創(chuàng)建新的紅酒提供商SellWineFactory和售賣紅酒接口SellWine在客戶端實(shí)例化一個(gè)代理對(duì)象,然后向該代理對(duì)象購買紅酒
  • 再回想「開閉原則:面向擴(kuò)展開放,面向修改關(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)到尾聲了,我們來總結(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è)接口,否則無法使用 JDK 動(dòng)態(tài)代理

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

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

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

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

  1. private static Class<?> getProxyClass0(ClassLoader loader, 
  2.                                        Class<?>... interfaces) { 
  3.     // 去代理類對(duì)象緩存中獲取代理類的 Class 對(duì)象 
  4.     return proxyClassCache.get(loader, interfaces); 

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

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

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

  1. private static final class ProxyClassFactory 
  2.     implements BiFunction<ClassLoader, Class<?>[], Class<?>> { 
  3.     // 非常重要,這就是我們看到的動(dòng)態(tài)代理的對(duì)象名前綴! 
  4.  private static final String proxyClassNamePrefix = "$Proxy"
  5.  
  6.     @Override 
  7.     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { 
  8.         Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); 
  9.         // 一些狀態(tài)校驗(yàn) 
  10.    
  11.         // 計(jì)數(shù)器,該計(jì)數(shù)器記錄了當(dāng)前已經(jīng)實(shí)例化多少個(gè)代理對(duì)象 
  12.         long num = nextUniqueNumber.getAndIncrement(); 
  13.         // 動(dòng)態(tài)代理對(duì)象名拼接!包名 + "$Proxy" + 數(shù)字 
  14.         String proxyName = proxyPkg + proxyClassNamePrefix + num; 
  15.  
  16.         // 生成字節(jié)碼文件,返回一個(gè)字節(jié)數(shù)組 
  17.         byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 
  18.             proxyName, interfaces, accessFlags); 
  19.         try { 
  20.             // 利用字節(jié)碼文件創(chuàng)建該字節(jié)碼的 Class 類對(duì)象 
  21.             return defineClass0(loader, proxyName, 
  22.                                 proxyClassFile, 0, proxyClassFile.length); 
  23.         } catch (ClassFormatError e) { 
  24.             throw new IllegalArgumentException(e.toString()); 
  25.         } 
  26.     } 

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)建類對(duì)象

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

 

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

CGLIB

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

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

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

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

(1)導(dǎo)入依賴

  1. <dependency> 
  2.     <groupId>cglib</groupId> 
  3.     <artifactId>cglib-nodep</artifactId> 
  4.     <version>3.3.0</version> 
  5.     <scope>test</scope> 
  6. </dependency> 

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

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

 

首先我們來定義代理工廠SellProxyFactory。

  1. public class SellProxyFactory implements MethodInterceptor { 
  2.     // 關(guān)聯(lián)真實(shí)對(duì)象,控制對(duì)真實(shí)對(duì)象的訪問 
  3.     private Object realObject; 
  4.     /** 從代理工廠中獲取一個(gè)代理對(duì)象實(shí)例,等價(jià)于創(chuàng)建小紅代理 */ 
  5.     public Object getProxyInstance(Object realObject) { 
  6.         this.realObject = realObject; 
  7.         Enhancer enhancer = new Enhancer(); 
  8.         // 設(shè)置需要增強(qiáng)類的類加載器 
  9.         enhancer.setClassLoader(realObject.getClass().getClassLoader()); 
  10.         // 設(shè)置被代理類,真實(shí)對(duì)象 
  11.         enhancer.setSuperclass(realObject.getClass()); 
  12.         // 設(shè)置方法攔截器,代理工廠 
  13.         enhancer.setCallback(this); 
  14.         // 創(chuàng)建代理類 
  15.         return enhancer.create(); 
  16.     } 
  17.      
  18.     @Override 
  19.     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 
  20.         doSomethingBefore(); // 前置增強(qiáng) 
  21.         Object object = methodProxy.invokeSuper(o, objects); 
  22.         doSomethingAfter(); // 后置增強(qiáng) 
  23.         return object; 
  24.     } 
  25.  
  26.     private void doSomethingBefore() { 
  27.         System.out.println("執(zhí)行方法前額外的操作..."); 
  28.     } 
  29.  
  30.     private void doSomethingAfter() { 
  31.         System.out.println("執(zhí)行方法后額外的操作..."); 
  32.     } 
  33.  

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

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

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

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

  1. public class XiaoMing { 
  2.     public static void main(String[] args) { 
  3.         SellProxyFactory sellProxyFactory = new SellProxyFactory(); 
  4.         // 獲取一個(gè)代理實(shí)例 
  5.         SellPerfumeFactory proxyInstance = 
  6.                 (SellPerfumeFactory) sellProxyFactory.getProxyInstance(new SellPerfumeFactory()); 
  7.         // 創(chuàng)建代理類 
  8.         proxyInstance.sellPerfume(1999.99); 
  9.     } 

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

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

(1)CGLIB 的使用方法:

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

JDK Proxy 和 CGLIB 的對(duì)比

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

  JDK Proxy CGLIB
代理工廠實(shí)現(xiàn)接口 InvocationHandler MethodInterceptor
構(gòu)造代理對(duì)象給 Client 服務(wù) Proxy Enhancer

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

  • 最明顯的不同:CGLIB 可以代理「大部分類」(第二點(diǎn)說到);而 JDK Proxy 「僅能夠代理實(shí)現(xiàn)了接口的類」
  • CGLIB 采用動(dòng)態(tài)創(chuàng)建被代理類的子類實(shí)現(xiàn)方法攔截,子類內(nèi)部重寫被攔截的方法,所以 CGLIB 不能代理被 final 關(guān)鍵字修飾的類和方法

細(xì)心的讀者會(huì)發(fā)現(xiàn),講的東西都是「淺嘗輒止」(你都沒有給我講源碼,水文實(shí)錘),動(dòng)態(tài)代理的精髓在于「程序在運(yùn)行時(shí)動(dòng)態(tà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ù)的代碼邏輯抽取出來形成一個(gè)單獨(dú)的覆蓋層」,在執(zhí)行代碼時(shí)可以將該覆蓋層毫無知覺的嵌入到原代碼邏輯里面去。

Spring AOP

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

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

2

 

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

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

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

  1. SqlSession session = null
  2. try{ 
  3.     session = getSqlSessionFactory().openSession(false); 
  4.     session.update("...", new Object()); 
  5.     // 事務(wù)提交 
  6.     session.commit(); 
  7. }catch(Exception e){ 
  8.     // 事務(wù)回滾 
  9.     session.rollback(); 
  10.     throw e; 
  11. }finally{ 
  12.     // 關(guān)閉事務(wù) 
  13.     session.close(); 

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

本文轉(zhuǎn)載自微信公眾號(hào)「 Java建設(shè)者」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Java建設(shè)者公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: Java建設(shè)者
相關(guān)推薦

2020-02-22 08:02:07

春節(jié)疫情防控口罩

2020-02-13 14:32:36

解決Maven沖突問題

2010-02-24 16:47:22

戴爾員工性騷擾

2019-06-18 10:02:06

CIO女性IT

2020-10-31 09:06:37

C語言編程語言

2022-05-07 07:33:55

TypeScript條件類型

2021-09-22 10:15:52

裁員選擇公司個(gè)人發(fā)展

2022-04-29 06:54:48

TS 映射類型User 類型

2010-11-15 10:57:26

2020-09-27 10:55:10

代碼Java字符串

2024-01-09 11:52:23

Rust開發(fā)函數(shù)

2015-06-11 11:19:35

2022-02-21 08:55:35

JavaClass文件代碼

2020-10-16 09:09:56

代碼業(yè)務(wù)模型

2022-12-31 08:56:46

CIOIT

2022-03-23 08:01:04

Python語言代碼

2021-11-10 06:38:01

Float Double 數(shù)據(jù)庫

2010-06-22 10:39:56

職場(chǎng)宮心計(jì)

2025-04-25 08:55:00

2021-09-18 08:06:17

數(shù)據(jù)庫MySQL技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)