溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

發(fā)布時間:2022-01-18 09:22:24 來源:億速云 閱讀:117 作者:柒染 欄目:大數(shù)據(jù)

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

1.AOP的概念:Aspect Oriented Programming 面向切面編程,可以通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能的一種技術(shù)。

2.OOP與AOP的區(qū)別:如果說面向?qū)ο缶幊淌顷P(guān)注將需求功能劃分為不同的并且相對獨立,封裝良好的類,并讓它們有著屬于自己的行為,依靠繼承和多態(tài)等來定義彼此的關(guān)系的話;那么面向方面編程則是希望能夠?qū)⑼ㄓ眯枨蠊δ軓牟幌嚓P(guān)的類當(dāng)中分離出來。

3.AOP的應(yīng)用范圍:日志記錄,性能統(tǒng)計,安全控制,事務(wù)處理,異常處理等等。 

切面Aspect:切面就是一個關(guān)注點的模塊化(橫切到代碼中的關(guān)注點),譬如:事務(wù)管理、日志記錄、權(quán)限管理都是所謂的切面。 連接點Joinpoint:程序執(zhí)行時的某個特定的點,在Spring中就是一個方法的執(zhí)行。  
通知Advice:通知就是在切面的某個連接點上執(zhí)行的操作,也就是事務(wù)管理、日志記錄等的具體代碼。 切入點Pointcut:切入點是指描述某一類特定的連接點,也就是說指定某一類要織入(Weave)通知的方法。  

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

Spring中的AOP編程的風(fēng)格主要有基于XML配置文件、基于注解兩種,我們這里以注解為例。

Spring的切入點匹配表達(dá)式使用AspectJ表達(dá)式(當(dāng)然也可以使用正則表達(dá)式等),也就是所謂的 Aspects,如下所示:

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

 (1.)?表示可以不配置,也就是說只有方法的名字name-pattern、方法的參數(shù)param-pattern是必須的。對于param-pattern之外的其余部分,可以使用*作為通配符,表示任意,例如:execution (* *.m(..))就是執(zhí)行任意返回值、任意類中的m方法時進(jìn)行織入。

  (2.)參數(shù)的通配配稍微復(fù)雜一些,其中(..)表示參數(shù)為0個或者多個,且類型任意;(*)表示任意類型的一個參數(shù);(*,String)表示第一個參數(shù)為任意類型,第二個參數(shù)為String類型;什么都不寫表示無參數(shù)。

  (3.)多個表達(dá)式可以使用&&、||、!進(jìn)行運算,因為表達(dá)式返回的是布爾值。

Spring中的AOP只針對方法,類型分為前置、后置、環(huán)繞、異常。 Spring的AOP是在運行時動態(tài)代理去完成,預(yù)編譯方式的AOP實現(xiàn)主要有Aspect,需要有專門JAVA編譯器,因為它是在編譯器進(jìn)行處理的。  

反射機制的概念:在運行時,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。 

說直白一些,就是我們可以于運行時加載、探知、使用編譯期間完全未知的類型,也就是具有看透任意的Class 的能力,哪怕這個Class是在運行時動態(tài)傳給你的完全未知的。
使用范圍:當(dāng)你要處理的類型無法在編譯器確定,而是在運行時動態(tài)傳入的。目前的MVC框架都采用這種機制。我們在Struts2框架中,如果想接收URL上的 參數(shù)?id=1&name=2&password=3,會很自然的在Action中寫上如下的內(nèi)容:
private Integer id;
private String name;
private String password;

及他們對應(yīng)的set方法,然后你就可以在Action中獲取上面的三個字段的值了??墒亲鳛镾truts2框架,Apache在開發(fā)的時候這個框架的時候,怎么知道我們Action里有這幾個字段的呢?因為Struts2里使用了反射看透了你的Action。

代理設(shè)計模式:為其他對象提供一種代理以控制對這個對象的訪問。說白了就是去掉客戶不能看到的內(nèi)容和服務(wù)或者增添客戶需要的額外服務(wù)。

常用的代理設(shè)計模式:

(1.)包裝:缺點就是當(dāng)Animal接口中增加了新的方法,那么包裝類中也必須增加這些新的方法。
(2.)繼承:缺點更明顯,那就是不能實現(xiàn)對接口的所有子類的代理,與包裝的模式相比,大大縮小了代理范圍。

但不管是那種方法,都是在編譯期完成的代理,不能像Spring那樣在運行期動態(tài)的對指定的類完成代理。

JAVA自帶的動態(tài)代理是基于java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler兩個類來完成的,使用JAVA反射機制。

Proxy類中的幾個方法都是靜態(tài)的,通常,你可以使用如下兩種模式創(chuàng)建代理對象:

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

第一種方式更加直接簡便,并且隱藏了代理$Proxy0對象的結(jié)構(gòu),回調(diào)InvocationHandler就是攔截處理的地方。

JDK的動態(tài)代理會動態(tài)的創(chuàng)建一個$Proxy0的類,這個類繼承了Proxy并且實現(xiàn)了要代理的目標(biāo)對象的接口,但你不要試圖在JDK中查找這個類,因為它是動態(tài)生成的。$Proxy0的結(jié)構(gòu)大致如下所示:

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

從上面的類結(jié)構(gòu),你就可以理解為什么第二種創(chuàng)建代理對象的方法為什么要那么寫了,以為它需要一個InvocationHandler作為構(gòu)造參數(shù)。

關(guān)于回調(diào)接口InvocationHandler:

它只有一個方法invoke()需要實現(xiàn),這個方法會在目標(biāo)對象的方法調(diào)用的時候被激活,你可以在這里控制目標(biāo)對象的方法的調(diào)用,在調(diào)用前后插入一些其他操作(譬如:鑒權(quán)、日志、事務(wù)管理等)。Invoke()方法的后兩個參數(shù)很好理解,一個是調(diào)用的方法的Method對象,另一個是方法的參數(shù),第一個參數(shù)有些需要注意的地方,這個proxy參數(shù)就是我們使用Proxy的靜態(tài)方法創(chuàng)建的動態(tài)代理對象,也就是$Proxy0的實例(這點你可以在Eclipse的斷點調(diào)試中看到proxy的所屬類型確實是$Proxy0)。由于$Proxy0在JDK中不是靜態(tài)存在的,因此你不可以把第一個參數(shù)Object proxy強制轉(zhuǎn)換為$Proxy0類型,因為你根本就無法從Classpath中導(dǎo)入$Proxy0。那么我們可以把proxy轉(zhuǎn)為目標(biāo)對象的接口嗎?因為$Proxy0是實現(xiàn)了目標(biāo)對象的所有的接口的,答案是可以的。但實際上這樣做的意義不大,因為你會發(fā)現(xiàn)轉(zhuǎn)換為目標(biāo)對象的接口之后,你調(diào)用接口中的任何一個方法,都會導(dǎo)致invoke()的調(diào)用陷入死循環(huán)而導(dǎo)致堆棧溢出。

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

這是因為目標(biāo)對象的大部分的方法都被代理了,你在invoke()通過代理對象轉(zhuǎn)換之后的接口調(diào)用目標(biāo)對象的方法,依然是走的代理對象,也就是說當(dāng)mammal.type()方法被激活時會立即導(dǎo)致invoke()的調(diào)用,然后再次調(diào)用mammal.type()方法,… …從而使方法調(diào)用進(jìn)入死循環(huán),就像無盡的遞歸調(diào)用。

那么invoke()方法的第一個參數(shù)到底干什么用的呢?其實一般情況下這個參數(shù)都用不到,除非你想獲得代理對象的類信息描述,因為它的getClass()方法的調(diào)用不會陷入死循環(huán)。為什么getClass()不會呢?因為getClass()方法是final的,不可以被覆蓋,所以也就不會被Proxy代理。但不要認(rèn)為Proxy不可以對final的方法進(jìn)行動態(tài)代理,因為Proxy面向的是Monkey的接口,而不是Monkey本身,所以即便是Monkey在實現(xiàn)Mammal、Primate接口的時候,把方法都變?yōu)閒inal的,也不會影響到Proxy的動態(tài)代理。

Proxy代理對象的過程如下所示

如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理

JDK的動態(tài)代理有個缺點,那就是不能對類進(jìn)行代理,只能對接口進(jìn)行代理,想象一下我們的Monkey如果沒有實現(xiàn)任何接口,那么將無法使用這種方式進(jìn)行動態(tài)代理(實際上是因為$Proxy0這個類繼承了Proxy,JAVA的繼承不允許出現(xiàn)多個父類)。但準(zhǔn)確的說這個問題不應(yīng)該是缺點,因為良好的系統(tǒng),每一個類都是應(yīng)該有一個接口的。

從上面知道$Proxy0是動態(tài)代理對象的所屬類型,但由于這個類型根本不存在,我們?nèi)绾舞b別一個對象是一個普通的對象還是動態(tài)代理對象呢?Proxy類中提供了isProxyClass(Class c)方法鑒別與此。
CGLIB是一個開源的動態(tài)代理框架,它的出現(xiàn)補充了JDK自帶的Proxy不能對類實現(xiàn)動態(tài)代理的問題。CGLIB是如何突破限制,對類也能動態(tài)代理的呢?這是因為CGLIB內(nèi)部使用了另一個字節(jié)碼框架ASM,類似的字節(jié)碼框架還有Javassist、BCEL等,但ASM被認(rèn)為是性能最好的一個。但這類字節(jié)碼框架要求你對JAVA的Class文件的結(jié)構(gòu)、指令集都比較了解,CGLIB對外屏蔽了這些細(xì)節(jié)問題。由于CGLIB使用ASM直接操作字節(jié)碼,因此效率要比Proxy高,但這里所說的效率是指代理對象的性能,在創(chuàng)建代理對象時,Proxy是要比CGLIB效率高的。
CGLIB動態(tài)代理的原理是使用ASM動態(tài)生成目標(biāo)對象的子類,final方法不能被子類覆蓋,自然也就不能被動態(tài)代理,這也是CGLIB的一個缺點。
CGLIB被Hibernate、Spring等很多開源框架在內(nèi)部使用,用于完成對類的動態(tài)代理,Spring中的很多XML配置屬性的proxy-target-class,默認(rèn)都為false,其含義就是默認(rèn)不啟用對目標(biāo)類的動態(tài)代理,而是對接口進(jìn)行動態(tài)代理。某些情況下,如果你想對Struts2的Action或者Spring MVC的Controller進(jìn)行動態(tài)代理,你會發(fā)現(xiàn)默認(rèn)Spring會報告找不到$Proxy0的xxx方法,這是因為一般我們都不會給控制層寫一個接口,而是直接在實現(xiàn)類中寫請求方法,這樣JDK自帶的Proxy是找不到這些方法的,因為他們不在接口中,此時你就要設(shè)置proxy-target-class=”true”,并引入CGLIB、ASM的類庫,Spring的動態(tài)代理就可以正常工作了。

關(guān)于如何進(jìn)行AOP開發(fā)中的Java動態(tài)代理問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI