javascript
从代理机制到Spring AOP,这篇给你安排的明明白白的
這篇文章準(zhǔn)備從Java的代理機(jī)制講到Spring的AOP。
1.代理模式
代理模式是很常見(jiàn)的一種設(shè)計(jì)模式,代理一詞拆開(kāi)來(lái)看就是代為受理,那顯然是要涉及到請(qǐng)求被代理的委托方,提供代理的代理方,以及想要通過(guò)代理來(lái)實(shí)際聯(lián)系委托方的客戶三個(gè)角色。
舉個(gè)生活中很常見(jiàn)的例子,各路的明星都會(huì)有個(gè)自己的經(jīng)紀(jì)人來(lái)替自己打點(diǎn)各種各樣的事情,這種場(chǎng)景下,明星本身是委托方,經(jīng)紀(jì)人是代理方,明星把自己安排演出、出席見(jiàn)面會(huì)的時(shí)間安排權(quán)利委托給經(jīng)紀(jì)人,這樣當(dāng)各個(gè)商家作為客戶想要請(qǐng)明星來(lái)代言時(shí),就只能通過(guò)經(jīng)紀(jì)人來(lái)進(jìn)行。
這樣明星本身不用暴露身份,而經(jīng)濟(jì)人也可以在溝通中告知商家明星出席活動(dòng)時(shí)要吃什么飯,做什么車(chē)的一些要求,省去了明星自己操心這些雞毛蒜皮小事兒。另一方面,當(dāng)經(jīng)紀(jì)人也可以給多個(gè)明星提供服務(wù),這樣商家只接觸一個(gè)經(jīng)紀(jì)人,可以聯(lián)系到不同的明星,找個(gè)適合自己公司的人選。
通過(guò)上面的例子,代理模式的優(yōu)點(diǎn)就顯而易見(jiàn)了:
*優(yōu)點(diǎn)一?*:可以隱藏委托類的實(shí)現(xiàn);
*優(yōu)點(diǎn)二?*:可以實(shí)現(xiàn)客戶與委托類間的解耦,在不修改委托類代碼的情況下能夠做一些額外的處理。
2.字節(jié)碼與代理模式
Java程序員都應(yīng)該知道,Java通過(guò)Java編譯器將.java源文件編譯成.class字節(jié)碼文件,這種.class文件是二進(jìn)制文件,內(nèi)容是只有JVM虛擬機(jī)能夠識(shí)別的機(jī)器碼,JVM虛擬機(jī)讀取字節(jié)碼文件,取出二進(jìn)制數(shù)據(jù),加載到內(nèi)存中,解析.class文件內(nèi)的信息,生成對(duì)應(yīng)的Class對(duì)象,進(jìn)而使Class對(duì)象創(chuàng)建類的具體實(shí)例來(lái)進(jìn)行調(diào)用實(shí)現(xiàn)具體的功能。
上圖說(shuō)明了Java加載字節(jié)碼的流程,但是Java的強(qiáng)大在于不僅僅可以加載在編譯期生成好的字節(jié)碼,還可以在運(yùn)行期系統(tǒng)中,遵循Java編譯系統(tǒng)組織.class文件的格式和結(jié)構(gòu),生成相應(yīng)的二進(jìn)制數(shù)據(jù),然后再把這個(gè)二進(jìn)制數(shù)據(jù)加載轉(zhuǎn)換成對(duì)應(yīng)的類,這樣,就完成了在代碼中,動(dòng)態(tài)創(chuàng)建一個(gè)類的能力了,如下圖流程。
下面舉一個(gè)動(dòng)態(tài)生成類的實(shí)例,通過(guò)Javassist實(shí)現(xiàn),Javassist是一個(gè)開(kāi)源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù),我們可以使用Javasisst工具在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建字節(jié)碼并加載類,如下代碼:
public?class?JavassistDemo?{public?static?void?main(String[]?args)?{makeNewClass();}public?static?Class<?>?makeNewClass()?{try?{//?獲取ClassPoolClassPool?pool?=?ClassPool.getDefault();//?創(chuàng)建Student類CtClass?ctClass?=?pool.makeClass("com.fufu.aop.Student");//?創(chuàng)建Student類成員變量nameCtField?name?=?new?CtField(pool.get("java.lang.String"),?"name",?ctClass);//?設(shè)置name為私有name.setModifiers(Modifier.PRIVATE);//?將name寫(xiě)入classctClass.addField(name,?CtField.Initializer.constant(""));?//寫(xiě)入class文件//增加set方法,名字為"setName"ctClass.addMethod(CtNewMethod.setter("setName",?name));//增加get方法,名字為getnamectClass.addMethod(CtNewMethod.getter("getName",?name));//?添加無(wú)參的構(gòu)造體CtConstructor?cons?=?new?CtConstructor(new?CtClass[]?{},?ctClass);cons.setBody("{name?=?\"Brant\";}");?//相當(dāng)于public?Sclass(){this.name?=?"brant";}ctClass.addConstructor(cons);//?添加有參的構(gòu)造體cons?=?new?CtConstructor(new?CtClass[]?{pool.get("java.lang.String")},?ctClass);cons.setBody("{$0.name?=?$1;}");??//第一個(gè)傳入的形參$1,第二個(gè)傳入的形參$2,相當(dāng)于public?Sclass(String?s){this.name?=?s;}ctClass.addConstructor(cons);//反射調(diào)用新創(chuàng)建的類Class<?>?aClass?=??ctClass?.toClass();Object?student?=?aClass.newInstance();Method?getter?=?null;getter?=?student.getClass().getMethod("getName");System.out.println(getter.invoke(student));}?catch?(Exception?e)?{e.printStackTrace();}return?null;} }介紹靜態(tài)和動(dòng)態(tài)加載字節(jié)碼的兩種方式,是為了引出下面關(guān)于兩種代理方式的介紹,代理機(jī)制通過(guò)代理類創(chuàng)建的時(shí)間不同分為了靜態(tài)代理和動(dòng)態(tài)代理:
*靜態(tài)代理?*:代理類在編譯階段生成,程序運(yùn)行前就已經(jīng)存在,那么這種代理方式被成為靜態(tài)代理,這種情況下的代理類通常都是我們?cè)贘ava代碼中定義的。
*動(dòng)態(tài)代理?*:代理類在程序運(yùn)行時(shí)創(chuàng)建,也就是說(shuō),這種情況下,代理類并不是在Java代碼中定義的,而是在運(yùn)行時(shí)根據(jù)我們?cè)贘ava代碼中的“指示”動(dòng)態(tài)生成的。
目前,靜態(tài)代理主要有AspectJ靜態(tài)代理、JDK靜態(tài)代理技術(shù)、而動(dòng)態(tài)代理有JDK動(dòng)態(tài)代理、Cglib動(dòng)態(tài)代理技術(shù),而Spring Aop是整合使用了JDK動(dòng)態(tài)代理和Cglib動(dòng)態(tài)代理兩種技術(shù),下面我們結(jié)合實(shí)例一步一步介紹所有的概念。
3.靜態(tài)代理
3.1 AspectJ靜態(tài)代理
對(duì)于AspectJ,我們只會(huì)進(jìn)行簡(jiǎn)單的了解,為后續(xù)理解打下基礎(chǔ),現(xiàn)在只需要知道下面這一句定義:
AspectJ是一個(gè)Java實(shí)現(xiàn)的面向切面的框架,它擴(kuò)展了Java語(yǔ)言。AspectJ有自定義的語(yǔ)法,所以它有一個(gè)專門(mén)的編譯器用來(lái)生成遵守Java字節(jié)編碼規(guī)范的Class文件。
注意上面定義中的“專門(mén)的編譯器”這個(gè)描述,可以看出AspectJ是典型的靜態(tài)代理技術(shù),因?yàn)槭窃诰幾g時(shí)期就生成了代理類,而使用AspectJ也肯定需要指定特定的編譯器,下面我們用AspectJ來(lái)實(shí)現(xiàn)上面的明星和經(jīng)紀(jì)人的模型。
首先在maven工程中引入AspectJ依賴:
??<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjtools</artifactId><version>1.8.9</version></dependency>然后在idea中將javac編譯器改為acj編譯器來(lái)支持AspectJ語(yǔ)法:
將明星的表演抽象成一個(gè)ShowService接口,包括了唱歌、跳舞的功能
public?interface?ShowService?{//?歌唱表演void?sing(String?songName);//?舞蹈表演void?dance(); }明星類實(shí)現(xiàn)了ShowService接口:
package?com.fufu.aop;public?class?Star?implements?ShowService{private?String?name;@Overridepublic?void?sing(String?songName)?{System.out.println(this.name?+?"?sing?a?song:?"?+?songName);}@Overridepublic?void?dance()?{System.out.println(this.name?+?"dance");}public?Star(String?name)?{this.name?=?name;}public?Star()?{}public?static?void?main(String[]?args)?{Star?star?=?new?Star("Eminem");star.sing("Mockingbird");} }用AspectJ語(yǔ)法實(shí)現(xiàn)一個(gè)代理AgentAspectJ:
package?com.fufu.aop;public?aspect?AgentAspectJ?{/***?定義切點(diǎn)*/pointcut?sleepPointCut():call(*?Star.sing(..));/***?定義切點(diǎn)*/pointcut?eatPointCut():call(*?Star.eat(..));/***?定義前置通知**?before(參數(shù)):連接點(diǎn)函數(shù){*?????函數(shù)體*?}*/before():sleepPointCut(){getMoney();}/***?定義后置通知*?after(參數(shù)):連接點(diǎn)函數(shù){*?????函數(shù)體*?}*/after():sleepPointCut(){writeReceipt();}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");} }創(chuàng)建一個(gè)Star并運(yùn)行方法:
public?static?void?main(String[]?args)?{Star?star?=?new?Star("Eminem");star.sing("Mockingbird");}輸出:
get?money Eminem?sing?a?song:?Mockingbird write?receipt可以看到Star的sing()方法前后輸出了我們?cè)贏gentAspectJ中定義的前置通知和后置通知,所以是AspectJ在編譯期間,根據(jù)AgentAspectJ代碼中定義的代碼,生成了增強(qiáng)的Star類,而我們實(shí)際調(diào)用時(shí),就會(huì)實(shí)現(xiàn)代理類的功能。
具體的AspectJ語(yǔ)法我們不深究,只需要知道pointcut是定義代理要代理的切入點(diǎn),這里是定義了兩個(gè)pointcut,分別是Star類的sing()方法和dance()方法。而before()和after()分別可以定義具體在切入點(diǎn)前后需要的額外操作。
總結(jié)一下,AspctJ就是用特定的編譯器和語(yǔ)法,對(duì)類實(shí)現(xiàn)編譯期增強(qiáng),實(shí)現(xiàn)靜態(tài)代理技術(shù),下面我們看JDK靜態(tài)代理。
3.2 JDK靜態(tài)代理
通常情況下, JDK靜態(tài)代理更多的是一種設(shè)計(jì)模式,JDK靜態(tài)代理的代理類和委托類會(huì)實(shí)現(xiàn)同一接口或是派生自相同的父類,代理模式的基本類圖入下:
?
我們接著通過(guò)把上面的明星和經(jīng)紀(jì)人的例子寫(xiě)成代碼來(lái)實(shí)現(xiàn)一個(gè)JDK靜態(tài)代理模式。
經(jīng)紀(jì)人類也實(shí)現(xiàn)了ShowService接口,持有了一個(gè)明星對(duì)象來(lái)提供真正的表演,并在各項(xiàng)表演的前后加入了經(jīng)紀(jì)人需要處理的事情,如收錢(qián)、開(kāi)發(fā)票等:
package?com.fufu.aop;/***?經(jīng)紀(jì)人*/ public?class?Agent?implements?ShowService{private?Star?star;public?Agent(Star?star)?{this.star?=?star;}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");}@Overridepublic?void?sing(String?songName)?{//?唱歌開(kāi)始前收錢(qián)getMoney();//?明星開(kāi)始唱歌star.sing(songName);//?唱歌結(jié)束后開(kāi)發(fā)票writeReceipt();}@Overridepublic?void?dance()?{//?跳舞開(kāi)始前收錢(qián)getMoney();//?明星開(kāi)始跳舞star.dance();//?跳舞結(jié)束后開(kāi)發(fā)票writeReceipt();} }通過(guò)經(jīng)紀(jì)人來(lái)請(qǐng)明星表演:
?public?static?void?main(String[]?args)?{Agent?agent?=?new?Agent(new?Star("Eminem"));agent.sing("Mockingbird");}輸出:
get?money Eminem?sing?a?song:?Mockingbird write?receipt以上就是一個(gè)典型的靜態(tài)代理的實(shí)例,很簡(jiǎn)單但是也能說(shuō)明問(wèn)題,我們來(lái)看看靜態(tài)代理的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):?業(yè)務(wù)類可以只關(guān)注自身邏輯,可以重用,通過(guò)代理類來(lái)增加通用的邏輯處理。
缺點(diǎn):
代理對(duì)象的一個(gè)接口只服務(wù)于一種類型的對(duì)象,如果要代理的類很多,勢(shì)必要為每一個(gè)類都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時(shí)就無(wú)法勝任了。
如果接口增加一個(gè)方法,除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度
另外,如果要按照上述的方法使用代理模式,那么真實(shí)角色(委托類)必須是事先已經(jīng)存在的,并將其作為代理對(duì)象的內(nèi)部屬性。但是實(shí)際使用時(shí),一個(gè)真實(shí)角色必須對(duì)應(yīng)一個(gè)代理角色,如果大量使用會(huì)導(dǎo)致類的急劇膨脹;此外,如果事先并不知道真實(shí)角色(委托類),該如何使用代理呢?這些問(wèn)題可以通過(guò)Java的動(dòng)態(tài)代理類來(lái)解決。
4.動(dòng)態(tài)代理
動(dòng)態(tài)代理類的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運(yùn)行時(shí)確定。
4.1 動(dòng)態(tài)代理思路
想弄明白動(dòng)態(tài)代理類實(shí)現(xiàn)的思路是什么,我們還需用從靜態(tài)代理的存在的問(wèn)題入手,因?yàn)楫吘箘?dòng)態(tài)代理是為了解決靜態(tài)代理存在問(wèn)題而出現(xiàn)的,回過(guò)頭來(lái)看靜態(tài)代理的問(wèn)題:
類膨脹:每個(gè)代理類都是一個(gè)需要程序員編寫(xiě)的具體類,不現(xiàn)實(shí)。
方法級(jí)代理:代理類和實(shí)現(xiàn)類都實(shí)現(xiàn)相同接口,導(dǎo)致代理類每個(gè)方法都需要進(jìn)行代理,你有幾個(gè)方法我就要有幾個(gè),編碼復(fù)雜,無(wú)法維護(hù)。
動(dòng)態(tài)代理如何解決:
第一個(gè)問(wèn)題很容易回答,類似使用Javasisst的例子,在代碼中動(dòng)態(tài)的創(chuàng)建代理類的字節(jié)碼,然后獲取到代理類對(duì)象。
第二問(wèn)題就要引出InvocationHandler了,為了構(gòu)造出具有通用性和簡(jiǎn)單性的代理類,可以將所有的觸發(fā)真實(shí)角色動(dòng)作交給一個(gè)觸發(fā)的管理器,讓這個(gè)管理器統(tǒng)一地管理觸發(fā)。這種管理器就是InvocationHandler。靜態(tài)代理中,代理類無(wú)非是在前后加入特定邏輯后,調(diào)用對(duì)應(yīng)的實(shí)現(xiàn)類的方法,sleep()對(duì)應(yīng)sleep(),run()對(duì)應(yīng)run(),而在Java中,方法Method也是一個(gè)對(duì)象,所以,動(dòng)態(tài)代理類可以將對(duì)自己的所有調(diào)用作為Method對(duì)象都交給InvocationHandler處理,InvocationHandler根據(jù)是什么Method調(diào)用具體實(shí)現(xiàn)類的不同方法,InvocationHandler負(fù)責(zé)增加代理邏輯和調(diào)用具體的實(shí)現(xiàn)類的方法。
也就是說(shuō),動(dòng)態(tài)代理類還是和實(shí)現(xiàn)類實(shí)現(xiàn)相同的接口,但是動(dòng)態(tài)代理類是根據(jù)實(shí)現(xiàn)類實(shí)現(xiàn)的接口動(dòng)態(tài)生成,不需要使用者關(guān)心,另外動(dòng)態(tài)代理類的所有方法調(diào)用,統(tǒng)一交給InvocationHandler,不用處理實(shí)現(xiàn)類每個(gè)接口的每個(gè)方法。
在這種模式之中:代理Proxy和RealSubject應(yīng)該實(shí)現(xiàn)相同的功能,這一點(diǎn)相當(dāng)重要。(我這里說(shuō)的功能,可以理解為某個(gè)類的public方法)
在面向?qū)ο蟮木幊讨?#xff0c;如果我們想要約定Proxy和RealSubject可以實(shí)現(xiàn)相同的功能,有兩種方式:
a.一個(gè)比較直觀的方式,就是定義一個(gè)功能接口,然后讓Proxy 和RealSubject來(lái)實(shí)現(xiàn)這個(gè)接口。b.還有比較隱晦的方式,就是通過(guò)繼承。因?yàn)槿绻鸓roxy繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還可以通過(guò)重寫(xiě)RealSubject中的方法,來(lái)實(shí)現(xiàn)多態(tài)。
其中JDK中提供的創(chuàng)建動(dòng)態(tài)代理的機(jī)制,是以a這種思路設(shè)計(jì)的,而cglib則是以b思路設(shè)計(jì)的。
4.1 JDK動(dòng)態(tài)代理(通過(guò)接口)
先來(lái)看一個(gè)具體的例子,還是以上邊明星和經(jīng)紀(jì)人的模型為例,這樣方便對(duì)比理解:
將明星的表演抽象成一個(gè)ShowService接口,包括了唱歌、跳舞的功能:
package?com.fufu.aop;public?interface?ShowService?{//?歌唱表演void?sing(String?songName);//?舞蹈表演void?dance(); }明星類實(shí)現(xiàn)了ShowService接口:
package?com.fufu.aop;/***?明星類*/ public?class?Star?implements?ShowService{private?String?name;@Overridepublic?void?sing(String?songName)?{System.out.println(this.name?+?"?sing?a?song:?"?+?songName);}@Overridepublic?void?dance()?{System.out.println(this.name?+?"dance");}public?Star(String?name)?{this.name?=?name;}public?Star()?{} }實(shí)現(xiàn)一個(gè)代理類的請(qǐng)求處理器,處理對(duì)具體類的所有方法的調(diào)用:
package?com.fufu.aop;import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Method;public?class?InvocationHandlerImpl?implements?InvocationHandler?{ShowService?target;public?InvocationHandlerImpl(ShowService?target)?{this.target?=?target;}@Overridepublic?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{//?表演開(kāi)始前收錢(qián)getMoney();//?明星開(kāi)始唱歌Object?invoke?=?method.invoke(target,?args);//?表演結(jié)束后開(kāi)發(fā)票writeReceipt();return?invoke;}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");} }通過(guò)JDK動(dòng)態(tài)代理機(jī)制實(shí)現(xiàn)一個(gè)動(dòng)態(tài)代理:
package?com.fufu.aop;import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Proxy;public?class?JDKProxyDemo?{public?static?void?main(String[]?args)?{//?1.創(chuàng)建被代理的具體類Star?star?=?new?Star("Eminem");//?2.獲取對(duì)應(yīng)的ClassLoaderClassLoader?classLoader?=?star.getClass().getClassLoader();//?3.獲取被代理對(duì)象實(shí)現(xiàn)的所有接口Class[]?interfaces?=?star.getClass().getInterfaces();//?4.設(shè)置請(qǐng)求處理器,處理所有方法調(diào)用InvocationHandler?invocationHandler?=?new?InvocationHandlerImpl(star);/***?5.根據(jù)上面提供的信息,創(chuàng)建代理對(duì)象?在這個(gè)過(guò)程中,*???a.JDK會(huì)通過(guò)根據(jù)傳入的參數(shù)信息動(dòng)態(tài)地在內(nèi)存中創(chuàng)建和.class文件等同的字節(jié)碼*???b.然后根據(jù)相應(yīng)的字節(jié)碼轉(zhuǎn)換成對(duì)應(yīng)的class,*???c.然后調(diào)用newInstance()創(chuàng)建實(shí)例*/Object?o?=?Proxy.newProxyInstance(classLoader,?interfaces,?invocationHandler);ShowService?showService?=?(ShowService)o;showService.sing("Mockingbird");} }我們從代理的創(chuàng)建入手,看看JDK的動(dòng)態(tài)代理都做了什么:
Object?o?=?Proxy.newProxyInstance(classLoader,?interfaces,?invocationHandler);Proxy.newProxyInstance()獲取Star類的所有接口列表(第二個(gè)參數(shù):interfaces)
確定要生成的代理類的類名,默認(rèn)為:com.sun.proxy.$ProxyXXXX
根據(jù)需要實(shí)現(xiàn)的接口信息,在代碼中動(dòng)態(tài)創(chuàng)建該P(yáng)roxy類的字節(jié)碼;
將對(duì)應(yīng)的字節(jié)碼轉(zhuǎn)換為對(duì)應(yīng)的class對(duì)象;
創(chuàng)建InvocationHandler實(shí)例handler,用來(lái)處理Proxy所有方法調(diào)用
Proxy的class對(duì)象以創(chuàng)建的handler對(duì)象為參數(shù)(第三個(gè)參數(shù):invocationHandler),實(shí)例化一個(gè)Proxy對(duì)象
而對(duì)于InvocationHandler,我們需要實(shí)現(xiàn)下列的invoke方法:
public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?在調(diào)用代理對(duì)象中的每一個(gè)方法時(shí),在代碼內(nèi)部,都是直接調(diào)用了InvocationHandler的invoke方法,而invoke方法根據(jù)代理類傳遞給自己的method參數(shù)來(lái)區(qū)分是什么方法。
可以看出,Proxy.newProxyInstance()方法生成的對(duì)象也是實(shí)現(xiàn)了ShowService接口的,所以可以在代碼中將其強(qiáng)制轉(zhuǎn)換為ShowService來(lái)使用,和靜態(tài)代理到達(dá)了同樣的效果。我們可以用下面代碼把生成的代理類的字節(jié)碼保存到磁盤(pán)里,然后反編譯看看JDK生成的動(dòng)態(tài)代理類的結(jié)構(gòu)。
package?com.fufu.aop;import?sun.misc.ProxyGenerator;import?java.io.FileOutputStream; import?java.io.IOException;public?class?ProxyUtils?{public?static?void?main(String[]?args)?{Star?star?=?new?Star("Eminem");generateClassFile(star.getClass(),?"StarProxy");}public?static?void?generateClassFile(Class?clazz,?String?proxyName)?{//根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼byte[]?classFile?=?ProxyGenerator.generateProxyClass(proxyName,?clazz.getInterfaces());String?paths?=?clazz.getResource(".").getPath();System.out.println(paths);FileOutputStream?out?=?null;try?{//保留到硬盤(pán)中out?=?new?FileOutputStream(paths?+?proxyName?+?".class");out.write(classFile);out.flush();}?catch?(Exception?e)?{e.printStackTrace();}?finally?{try?{out.close();}?catch?(IOException?e)?{e.printStackTrace();}}} }反編譯StarPoxy.class文件后得到:
// //?Source?code?recreated?from?a?.class?file?by?IntelliJ?IDEA //?(powered?by?Fernflower?decompiler) //import?com.fufu.aop.ShowService; import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Method; import?java.lang.reflect.Proxy; import?java.lang.reflect.UndeclaredThrowableException;//?動(dòng)態(tài)代理類StarPoxy實(shí)現(xiàn)了ShowService接口 public?final?class?StarProxy?extends?Proxy?implements?ShowService?{//?加載接口中定義的所有方法private?static?Method?m1;private?static?Method?m3;private?static?Method?m4;private?static?Method?m2;private?static?Method?m0;//構(gòu)造函數(shù)接入InvocationHandler,也就是持有了InvocationHandler對(duì)象hpublic?StarProxy(InvocationHandler?var1)?throws??{super(var1);}public?final?boolean?equals(Object?var1)?throws??{try?{return?((Boolean)super.h.invoke(this,?m1,?new?Object[]{var1})).booleanValue();}?catch?(RuntimeException?|?Error?var3)?{throw?var3;}?catch?(Throwable?var4)?{throw?new?UndeclaredThrowableException(var4);}}//?自動(dòng)生成的sing()方法,實(shí)際調(diào)用InvocationHandler對(duì)象h的invoke方法,傳入m3參數(shù)對(duì)象代表sing()方法public?final?void?sing(String?var1)?throws??{try?{super.h.invoke(this,?m3,?new?Object[]{var1});}?catch?(RuntimeException?|?Error?var3)?{throw?var3;}?catch?(Throwable?var4)?{throw?new?UndeclaredThrowableException(var4);}}//同理生成dance()方法public?final?void?dance()?throws??{try?{super.h.invoke(this,?m4,?(Object[])null);}?catch?(RuntimeException?|?Error?var2)?{throw?var2;}?catch?(Throwable?var3)?{throw?new?UndeclaredThrowableException(var3);}}public?final?String?toString()?throws??{try?{return?(String)super.h.invoke(this,?m2,?(Object[])null);}?catch?(RuntimeException?|?Error?var2)?{throw?var2;}?catch?(Throwable?var3)?{throw?new?UndeclaredThrowableException(var3);}}public?final?int?hashCode()?throws??{try?{return?((Integer)super.h.invoke(this,?m0,?(Object[])null)).intValue();}?catch?(RuntimeException?|?Error?var2)?{throw?var2;}?catch?(Throwable?var3)?{throw?new?UndeclaredThrowableException(var3);}}//?加載接口中定義的所有方法static?{try?{m1?=?Class.forName("java.lang.Object").getMethod("equals",?new?Class[]{Class.forName("java.lang.Object")});m3?=?Class.forName("com.fufu.aop.ShowService").getMethod("sing",?new?Class[]{Class.forName("java.lang.String")});m4?=?Class.forName("com.fufu.aop.ShowService").getMethod("dance",?new?Class[0]);m2?=?Class.forName("java.lang.Object").getMethod("toString",?new?Class[0]);m0?=?Class.forName("java.lang.Object").getMethod("hashCode",?new?Class[0]);}?catch?(NoSuchMethodException?var2)?{throw?new?NoSuchMethodError(var2.getMessage());}?catch?(ClassNotFoundException?var3)?{throw?new?NoClassDefFoundError(var3.getMessage());}} }通過(guò)上面反編譯后的代碼可以看出,JDK生成的動(dòng)態(tài)代理類實(shí)現(xiàn)和具體類相同的接口,并持有InvocationHandler對(duì)象(InvocationHandler對(duì)象又持有具體類),調(diào)用動(dòng)態(tài)代理類中方法,會(huì)觸發(fā)傳入InvocationHandler的invoke()方法,通過(guò)method參數(shù),來(lái)區(qū)分調(diào)用的是什么具體的方法,具體如下圖所示:
4.2 CGLIB動(dòng)態(tài)代理(通過(guò)繼承)
JDK中提供的生成動(dòng)態(tài)代理類的機(jī)制有個(gè)鮮明的特點(diǎn)是:
某個(gè)類必須有實(shí)現(xiàn)的接口,而生成的代理類也只能代理某個(gè)類接口定義的方法,比如:如果上面例子的Star實(shí)現(xiàn)了繼承自ShowService接口的方法外,另外實(shí)現(xiàn)了方法play(),則在產(chǎn)生的動(dòng)態(tài)代理類中不會(huì)有這個(gè)方法了!更極端的情況是:如果某個(gè)類沒(méi)有實(shí)現(xiàn)接口,那么這個(gè)類就不能用JDK產(chǎn)生動(dòng)態(tài)代理了!
幸好我們有cglib,“CGLIB(Code Generation Library),是一個(gè)強(qiáng)大的,高性能,高質(zhì)量的Code生成類庫(kù),它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口。”
cglib 創(chuàng)建某個(gè)類A的動(dòng)態(tài)代理類的模式是:
查找A上的所有非final 的public類型的方法定義;
將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對(duì)象;
實(shí)現(xiàn) MethodInterceptor接口,用來(lái)處理對(duì)代理類上所有方法的請(qǐng)求(這個(gè)接口和JDK動(dòng)態(tài)代理InvocationHandler的功能和角色是一樣的)
有了上邊JDK動(dòng)態(tài)代理的例子,cglib的理解起來(lái)就簡(jiǎn)單了,還是先以實(shí)例說(shuō)明,ShowService接口和Star類都復(fù)用之前的不變:
實(shí)現(xiàn) MethodInterceptor接口:
package?com.fufu.aop;import?net.sf.cglib.proxy.MethodInterceptor; import?net.sf.cglib.proxy.MethodProxy;import?java.lang.reflect.Method;public?class?MethodInterceptorImpl?implements?MethodInterceptor?{@Overridepublic?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{//?表演開(kāi)始前收錢(qián)getMoney();//?明星開(kāi)始唱歌Object?invoke?=?methodProxy.invokeSuper(o,?objects);//?表演結(jié)束后開(kāi)發(fā)票writeReceipt();return?invoke;}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");} }創(chuàng)建動(dòng)態(tài)代理:
package?com.fufu.aop;import?net.sf.cglib.proxy.Enhancer; import?net.sf.cglib.proxy.MethodInterceptor;public?class?CglibProxyDemo?{public?static?void?main(String[]?args)?{Star?star?=?new?Star("Eminem");MethodInterceptor?methodInterceptor?=?new?MethodInterceptorImpl();//cglib?中加強(qiáng)器,用來(lái)創(chuàng)建動(dòng)態(tài)代理Enhancer?enhancer?=?new?Enhancer();//設(shè)置要?jiǎng)?chuàng)建動(dòng)態(tài)代理的類enhancer.setSuperclass(star.getClass());//?設(shè)置回調(diào),這里相當(dāng)于是對(duì)于代理類上所有方法的調(diào)用,都會(huì)調(diào)用CallBack,而Callback則需要實(shí)行intercept()方法進(jìn)行攔截enhancer.setCallback(methodInterceptor);ShowService?showService?=?(ShowService)?enhancer.create();showService.sing("Mockingbird");} }通過(guò)以上實(shí)例可以看出,Cglib通過(guò)繼承實(shí)現(xiàn)動(dòng)態(tài)代理,具體類不需要實(shí)現(xiàn)特定的接口,而且代理類可以調(diào)用具體類的非接口方法,更加靈活。
5.Spring AOP
5.1 概念
AOP的具體概念就不再說(shuō)了,網(wǎng)上一搜一大把,這篇文章主要介紹Spring AOP低層使用的代理技術(shù),因?yàn)槠綍r(shí)在使用Spring AOP時(shí),很多人都是copy配置,對(duì)上面介紹的這些技術(shù)概念并不清楚。
Spring AOP采用的是動(dòng)態(tài)代理,在運(yùn)行期間對(duì)業(yè)務(wù)方法進(jìn)行增強(qiáng),所以不會(huì)生成新類,對(duì)于動(dòng)態(tài)代理技術(shù),Spring AOP提供了對(duì)JDK動(dòng)態(tài)代理的支持以及CGLib的支持,然而什么時(shí)候用哪種代理呢?
1、如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP
2、如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP
3、如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)了接口,必須采用CGLIB庫(kù),spring會(huì)自動(dòng)在JDK動(dòng)態(tài)代理和CGLIB之間轉(zhuǎn)換
目前來(lái)看,Spring貌似和AspectJ沒(méi)半毛錢(qián)關(guān)系,那為什么在許多應(yīng)用了Spring AOP的項(xiàng)目中都出現(xiàn)了@AspectJ的注解呢?Spring是應(yīng)用的動(dòng)態(tài)代理,怎么會(huì)還和AspectJ有關(guān)系呢,原因是Spring AOP基于注解配置的情況下,需要依賴于AspectJ包的標(biāo)準(zhǔn)注解,但是不需要額外的編譯以及AspectJ的織入器,而基于XML配置不需要,所以Spring AOP只是復(fù)用了AspectJ的注解,并沒(méi)有其他依賴AspectJ的地方。
當(dāng)Spring需要使用@AspectJ注解支持時(shí),需要在Spring配置文件中如下配置:
<aop:aspectj-autoproxy/>?而關(guān)于第二點(diǎn)強(qiáng)制使用CGLIB,可以通過(guò)在Spring的配置文件如下配置實(shí)現(xiàn):
<aop:aspectj-autoproxy?proxy-target-class="true"/>?proxy-target-class屬性值決定是基于接口的還是基于類的代理被創(chuàng)建。如果proxy-target-class 屬性值被設(shè)置為true,那么基于類的代理將起作用(這時(shí)需要cglib庫(kù))。如果proxy-target-class屬值被設(shè)置為false或者這個(gè)屬性被省略,那么標(biāo)準(zhǔn)的JDK 基于接口的代理。
所以,雖然使用了Aspect的Annotation,但是并沒(méi)有使用它的編譯器和織入器。其實(shí)現(xiàn)原理是JDK動(dòng)態(tài)代理或Cglib,在運(yùn)行時(shí)生成代理類。
已經(jīng)寫(xiě)了這么多了,下面再貼兩個(gè)Spring AOP的demo代碼吧,分別是基于XML和注解的:
5.2 基于XML
切面類:
package?com.fufu.spring.aop;import?org.springframework.stereotype.Component;/***?基于XML的Spring?AOP*/ @Component public?class?AgentAdvisorXML?{public?void?getMoney()?{System.out.println("get?money");}public?void?writeReceipt()?{System.out.println("write?receipt");} }配置文件:
<?xml?version="1.0"?encoding="UTF-8"?> <beans?xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean?id="star"?class="com.fufu.proxy.Star"><property?name="name"?value="Eminem"/></bean><bean?id="agentAdvisorXML"?class="com.fufu.spring.aop.AgentAdvisorXML"/><!--Spring基于Xml的切面--><aop:config><!--?定義切點(diǎn)函數(shù)?--><aop:pointcut?id="singPointCut"?expression="execution(*?com.fufu.proxy.Star.sing(..))"/><!--?定義切面?order?定義優(yōu)先級(jí),值越小優(yōu)先級(jí)越大--><aop:aspect?ref="agentAdvisorXML"?order="0"><!--前置通知--><aop:before?method="getMoney"?pointcut-ref="singPointCut"/><!--后置通知--><aop:after?method="writeReceipt"?pointcut-ref="singPointCut"/></aop:aspect></aop:config></beans>測(cè)試類:
package?com.fufu.spring.aop;import?com.fufu.proxy.ShowService; import?org.springframework.context.support.ClassPathXmlApplicationContext;public?class?Main?{public?static?void?main(String[]?args)?{ClassPathXmlApplicationContext?applicationContext?=?new?ClassPathXmlApplicationContext("spring-aop.xml");Object?star?=?applicationContext.getBean("star");ShowService?showService?=?(ShowService)star;showService.sing("Mockingbird");} }5.3 基于注解
切面類:
package?com.fufu.spring.aop;import?org.aspectj.lang.annotation.After; import?org.aspectj.lang.annotation.Aspect; import?org.aspectj.lang.annotation.Before; import?org.springframework.stereotype.Component;/***?基于注解的Spring?AOP*/ @Aspect @Component public?class?AgentAdvisor?{@Before(value?=?"execution(*?com.fufu.proxy.ShowService.sing(..))")public?void?getMoney()?{System.out.println("get?money");}@After(value?=?"execution(*?com.fufu.proxy.ShowService.sing(..))")public?void?writeReceipt()?{System.out.println("write?receipt");} }配置文件:
<?xml?version="1.0"?encoding="UTF-8"?> <beans?xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan?base-package="com.fufu.proxy,?com.fufu.spring.aop"/><aop:aspectj-autoproxy??proxy-target-class="true"/></beans>測(cè)試類:
package?com.fufu.spring.aop;import?com.fufu.proxy.ShowService; import?org.springframework.context.support.ClassPathXmlApplicationContext;public?class?Main?{public?static?void?main(String[]?args)?{ClassPathXmlApplicationContext?applicationContext?=?new?ClassPathXmlApplicationContext("spring-aop-annotation.xml");Object?star?=?applicationContext.getBean("star");ShowService?showService?=?(ShowService)star;showService.sing("Mockingbird");} }6.總結(jié)
以上內(nèi)容,雖然比較淺顯易懂,但是可以對(duì)Java代理機(jī)制和Spring AOP會(huì)有一個(gè)全面的理解,如有錯(cuò)誤,歡迎指正。
總結(jié)
以上是生活随笔為你收集整理的从代理机制到Spring AOP,这篇给你安排的明明白白的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 亿级大表分库分表实战总结(万字干货,实战
- 下一篇: JScrollPane实现自动滚动到底部