javascript
从代理机制到Spring AOP,这篇给你安排的明明白白的
這篇文章準備從Java的代理機制講到Spring的AOP。
1.代理模式
代理模式是很常見的一種設計模式,代理一詞拆開來看就是代為受理,那顯然是要涉及到請求被代理的委托方,提供代理的代理方,以及想要通過代理來實際聯系委托方的客戶三個角色。
舉個生活中很常見的例子,各路的明星都會有個自己的經紀人來替自己打點各種各樣的事情,這種場景下,明星本身是委托方,經紀人是代理方,明星把自己安排演出、出席見面會的時間安排權利委托給經紀人,這樣當各個商家作為客戶想要請明星來代言時,就只能通過經紀人來進行。
這樣明星本身不用暴露身份,而經濟人也可以在溝通中告知商家明星出席活動時要吃什么飯,做什么車的一些要求,省去了明星自己操心這些雞毛蒜皮小事兒。另一方面,當經紀人也可以給多個明星提供服務,這樣商家只接觸一個經紀人,可以聯系到不同的明星,找個適合自己公司的人選。
通過上面的例子,代理模式的優點就顯而易見了:
*優點一?*:可以隱藏委托類的實現;
*優點二?*:可以實現客戶與委托類間的解耦,在不修改委托類代碼的情況下能夠做一些額外的處理。
2.字節碼與代理模式
Java程序員都應該知道,Java通過Java編譯器將.java源文件編譯成.class字節碼文件,這種.class文件是二進制文件,內容是只有JVM虛擬機能夠識別的機器碼,JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class文件內的信息,生成對應的Class對象,進而使Class對象創建類的具體實例來進行調用實現具體的功能。
上圖說明了Java加載字節碼的流程,但是Java的強大在于不僅僅可以加載在編譯期生成好的字節碼,還可以在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,然后再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態創建一個類的能力了,如下圖流程。
下面舉一個動態生成類的實例,通過Javassist實現,Javassist是一個開源的分析、編輯和創建Java字節碼的類庫,我們可以使用Javasisst工具在運行時動態創建字節碼并加載類,如下代碼:
public?class?JavassistDemo?{public?static?void?main(String[]?args)?{makeNewClass();}public?static?Class<?>?makeNewClass()?{try?{//?獲取ClassPoolClassPool?pool?=?ClassPool.getDefault();//?創建Student類CtClass?ctClass?=?pool.makeClass("com.fufu.aop.Student");//?創建Student類成員變量nameCtField?name?=?new?CtField(pool.get("java.lang.String"),?"name",?ctClass);//?設置name為私有name.setModifiers(Modifier.PRIVATE);//?將name寫入classctClass.addField(name,?CtField.Initializer.constant(""));?//寫入class文件//增加set方法,名字為"setName"ctClass.addMethod(CtNewMethod.setter("setName",?name));//增加get方法,名字為getnamectClass.addMethod(CtNewMethod.getter("getName",?name));//?添加無參的構造體CtConstructor?cons?=?new?CtConstructor(new?CtClass[]?{},?ctClass);cons.setBody("{name?=?\"Brant\";}");?//相當于public?Sclass(){this.name?=?"brant";}ctClass.addConstructor(cons);//?添加有參的構造體cons?=?new?CtConstructor(new?CtClass[]?{pool.get("java.lang.String")},?ctClass);cons.setBody("{$0.name?=?$1;}");??//第一個傳入的形參$1,第二個傳入的形參$2,相當于public?Sclass(String?s){this.name?=?s;}ctClass.addConstructor(cons);//反射調用新創建的類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;} }介紹靜態和動態加載字節碼的兩種方式,是為了引出下面關于兩種代理方式的介紹,代理機制通過代理類創建的時間不同分為了靜態代理和動態代理:
*靜態代理?*:代理類在編譯階段生成,程序運行前就已經存在,那么這種代理方式被成為靜態代理,這種情況下的代理類通常都是我們在Java代碼中定義的。
*動態代理?*:代理類在程序運行時創建,也就是說,這種情況下,代理類并不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。
目前,靜態代理主要有AspectJ靜態代理、JDK靜態代理技術、而動態代理有JDK動態代理、Cglib動態代理技術,而Spring Aop是整合使用了JDK動態代理和Cglib動態代理兩種技術,下面我們結合實例一步一步介紹所有的概念。
3.靜態代理
3.1 AspectJ靜態代理
對于AspectJ,我們只會進行簡單的了解,為后續理解打下基礎,現在只需要知道下面這一句定義:
AspectJ是一個Java實現的面向切面的框架,它擴展了Java語言。AspectJ有自定義的語法,所以它有一個專門的編譯器用來生成遵守Java字節編碼規范的Class文件。
注意上面定義中的“專門的編譯器”這個描述,可以看出AspectJ是典型的靜態代理技術,因為是在編譯時期就生成了代理類,而使用AspectJ也肯定需要指定特定的編譯器,下面我們用AspectJ來實現上面的明星和經紀人的模型。
首先在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編譯器來支持AspectJ語法:
將明星的表演抽象成一個ShowService接口,包括了唱歌、跳舞的功能
public?interface?ShowService?{//?歌唱表演void?sing(String?songName);//?舞蹈表演void?dance(); }明星類實現了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語法實現一個代理AgentAspectJ:
package?com.fufu.aop;public?aspect?AgentAspectJ?{/***?定義切點*/pointcut?sleepPointCut():call(*?Star.sing(..));/***?定義切點*/pointcut?eatPointCut():call(*?Star.eat(..));/***?定義前置通知**?before(參數):連接點函數{*?????函數體*?}*/before():sleepPointCut(){getMoney();}/***?定義后置通知*?after(參數):連接點函數{*?????函數體*?}*/after():sleepPointCut(){writeReceipt();}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");} }創建一個Star并運行方法:
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()方法前后輸出了我們在AgentAspectJ中定義的前置通知和后置通知,所以是AspectJ在編譯期間,根據AgentAspectJ代碼中定義的代碼,生成了增強的Star類,而我們實際調用時,就會實現代理類的功能。
具體的AspectJ語法我們不深究,只需要知道pointcut是定義代理要代理的切入點,這里是定義了兩個pointcut,分別是Star類的sing()方法和dance()方法。而before()和after()分別可以定義具體在切入點前后需要的額外操作。
總結一下,AspctJ就是用特定的編譯器和語法,對類實現編譯期增強,實現靜態代理技術,下面我們看JDK靜態代理。
3.2 JDK靜態代理
通常情況下, JDK靜態代理更多的是一種設計模式,JDK靜態代理的代理類和委托類會實現同一接口或是派生自相同的父類,代理模式的基本類圖入下:
?
我們接著通過把上面的明星和經紀人的例子寫成代碼來實現一個JDK靜態代理模式。
經紀人類也實現了ShowService接口,持有了一個明星對象來提供真正的表演,并在各項表演的前后加入了經紀人需要處理的事情,如收錢、開發票等:
package?com.fufu.aop;/***?經紀人*/ 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)?{//?唱歌開始前收錢getMoney();//?明星開始唱歌star.sing(songName);//?唱歌結束后開發票writeReceipt();}@Overridepublic?void?dance()?{//?跳舞開始前收錢getMoney();//?明星開始跳舞star.dance();//?跳舞結束后開發票writeReceipt();} }通過經紀人來請明星表演:
?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以上就是一個典型的靜態代理的實例,很簡單但是也能說明問題,我們來看看靜態代理的優缺點:
優點:?業務類可以只關注自身邏輯,可以重用,通過代理類來增加通用的邏輯處理。
缺點:
代理對象的一個接口只服務于一種類型的對象,如果要代理的類很多,勢必要為每一個類都進行代理,靜態代理在程序規模稍大時就無法勝任了。
如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度
另外,如果要按照上述的方法使用代理模式,那么真實角色(委托類)必須是事先已經存在的,并將其作為代理對象的內部屬性。但是實際使用時,一個真實角色必須對應一個代理角色,如果大量使用會導致類的急劇膨脹;此外,如果事先并不知道真實角色(委托類),該如何使用代理呢?這些問題可以通過Java的動態代理類來解決。
4.動態代理
動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委托類的關系是在程序運行時確定。
4.1 動態代理思路
想弄明白動態代理類實現的思路是什么,我們還需用從靜態代理的存在的問題入手,因為畢竟動態代理是為了解決靜態代理存在問題而出現的,回過頭來看靜態代理的問題:
類膨脹:每個代理類都是一個需要程序員編寫的具體類,不現實。
方法級代理:代理類和實現類都實現相同接口,導致代理類每個方法都需要進行代理,你有幾個方法我就要有幾個,編碼復雜,無法維護。
動態代理如何解決:
第一個問題很容易回答,類似使用Javasisst的例子,在代碼中動態的創建代理類的字節碼,然后獲取到代理類對象。
第二問題就要引出InvocationHandler了,為了構造出具有通用性和簡單性的代理類,可以將所有的觸發真實角色動作交給一個觸發的管理器,讓這個管理器統一地管理觸發。這種管理器就是InvocationHandler。靜態代理中,代理類無非是在前后加入特定邏輯后,調用對應的實現類的方法,sleep()對應sleep(),run()對應run(),而在Java中,方法Method也是一個對象,所以,動態代理類可以將對自己的所有調用作為Method對象都交給InvocationHandler處理,InvocationHandler根據是什么Method調用具體實現類的不同方法,InvocationHandler負責增加代理邏輯和調用具體的實現類的方法。
也就是說,動態代理類還是和實現類實現相同的接口,但是動態代理類是根據實現類實現的接口動態生成,不需要使用者關心,另外動態代理類的所有方法調用,統一交給InvocationHandler,不用處理實現類每個接口的每個方法。
在這種模式之中:代理Proxy和RealSubject應該實現相同的功能,這一點相當重要。(我這里說的功能,可以理解為某個類的public方法)
在面向對象的編程之中,如果我們想要約定Proxy和RealSubject可以實現相同的功能,有兩種方式:
a.一個比較直觀的方式,就是定義一個功能接口,然后讓Proxy 和RealSubject來實現這個接口。b.還有比較隱晦的方式,就是通過繼承。因為如果Proxy繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還可以通過重寫RealSubject中的方法,來實現多態。
其中JDK中提供的創建動態代理的機制,是以a這種思路設計的,而cglib則是以b思路設計的。
4.1 JDK動態代理(通過接口)
先來看一個具體的例子,還是以上邊明星和經紀人的模型為例,這樣方便對比理解:
將明星的表演抽象成一個ShowService接口,包括了唱歌、跳舞的功能:
package?com.fufu.aop;public?interface?ShowService?{//?歌唱表演void?sing(String?songName);//?舞蹈表演void?dance(); }明星類實現了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()?{} }實現一個代理類的請求處理器,處理對具體類的所有方法的調用:
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?{//?表演開始前收錢getMoney();//?明星開始唱歌Object?invoke?=?method.invoke(target,?args);//?表演結束后開發票writeReceipt();return?invoke;}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");} }通過JDK動態代理機制實現一個動態代理:
package?com.fufu.aop;import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Proxy;public?class?JDKProxyDemo?{public?static?void?main(String[]?args)?{//?1.創建被代理的具體類Star?star?=?new?Star("Eminem");//?2.獲取對應的ClassLoaderClassLoader?classLoader?=?star.getClass().getClassLoader();//?3.獲取被代理對象實現的所有接口Class[]?interfaces?=?star.getClass().getInterfaces();//?4.設置請求處理器,處理所有方法調用InvocationHandler?invocationHandler?=?new?InvocationHandlerImpl(star);/***?5.根據上面提供的信息,創建代理對象?在這個過程中,*???a.JDK會通過根據傳入的參數信息動態地在內存中創建和.class文件等同的字節碼*???b.然后根據相應的字節碼轉換成對應的class,*???c.然后調用newInstance()創建實例*/Object?o?=?Proxy.newProxyInstance(classLoader,?interfaces,?invocationHandler);ShowService?showService?=?(ShowService)o;showService.sing("Mockingbird");} }我們從代理的創建入手,看看JDK的動態代理都做了什么:
Object?o?=?Proxy.newProxyInstance(classLoader,?interfaces,?invocationHandler);Proxy.newProxyInstance()獲取Star類的所有接口列表(第二個參數:interfaces)
確定要生成的代理類的類名,默認為:com.sun.proxy.$ProxyXXXX
根據需要實現的接口信息,在代碼中動態創建該Proxy類的字節碼;
將對應的字節碼轉換為對應的class對象;
創建InvocationHandler實例handler,用來處理Proxy所有方法調用
Proxy的class對象以創建的handler對象為參數(第三個參數:invocationHandler),實例化一個Proxy對象
而對于InvocationHandler,我們需要實現下列的invoke方法:
public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?在調用代理對象中的每一個方法時,在代碼內部,都是直接調用了InvocationHandler的invoke方法,而invoke方法根據代理類傳遞給自己的method參數來區分是什么方法。
可以看出,Proxy.newProxyInstance()方法生成的對象也是實現了ShowService接口的,所以可以在代碼中將其強制轉換為ShowService來使用,和靜態代理到達了同樣的效果。我們可以用下面代碼把生成的代理類的字節碼保存到磁盤里,然后反編譯看看JDK生成的動態代理類的結構。
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)?{//根據類信息和提供的代理類名稱,生成字節碼byte[]?classFile?=?ProxyGenerator.generateProxyClass(proxyName,?clazz.getInterfaces());String?paths?=?clazz.getResource(".").getPath();System.out.println(paths);FileOutputStream?out?=?null;try?{//保留到硬盤中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;//?動態代理類StarPoxy實現了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;//構造函數接入InvocationHandler,也就是持有了InvocationHandler對象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);}}//?自動生成的sing()方法,實際調用InvocationHandler對象h的invoke方法,傳入m3參數對象代表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());}} }通過上面反編譯后的代碼可以看出,JDK生成的動態代理類實現和具體類相同的接口,并持有InvocationHandler對象(InvocationHandler對象又持有具體類),調用動態代理類中方法,會觸發傳入InvocationHandler的invoke()方法,通過method參數,來區分調用的是什么具體的方法,具體如下圖所示:
4.2 CGLIB動態代理(通過繼承)
JDK中提供的生成動態代理類的機制有個鮮明的特點是:
某個類必須有實現的接口,而生成的代理類也只能代理某個類接口定義的方法,比如:如果上面例子的Star實現了繼承自ShowService接口的方法外,另外實現了方法play(),則在產生的動態代理類中不會有這個方法了!更極端的情況是:如果某個類沒有實現接口,那么這個類就不能用JDK產生動態代理了!
幸好我們有cglib,“CGLIB(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口?!?/p>
cglib 創建某個類A的動態代理類的模式是:
查找A上的所有非final 的public類型的方法定義;
將這些方法的定義轉換成字節碼;
將組成的字節碼轉換成相應的代理的class對象;
實現 MethodInterceptor接口,用來處理對代理類上所有方法的請求(這個接口和JDK動態代理InvocationHandler的功能和角色是一樣的)
有了上邊JDK動態代理的例子,cglib的理解起來就簡單了,還是先以實例說明,ShowService接口和Star類都復用之前的不變:
實現 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?{//?表演開始前收錢getMoney();//?明星開始唱歌Object?invoke?=?methodProxy.invokeSuper(o,?objects);//?表演結束后開發票writeReceipt();return?invoke;}private?void?getMoney()?{System.out.println("get?money");}private?void?writeReceipt()?{System.out.println("write?receipt");} }創建動態代理:
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?中加強器,用來創建動態代理Enhancer?enhancer?=?new?Enhancer();//設置要創建動態代理的類enhancer.setSuperclass(star.getClass());//?設置回調,這里相當于是對于代理類上所有方法的調用,都會調用CallBack,而Callback則需要實行intercept()方法進行攔截enhancer.setCallback(methodInterceptor);ShowService?showService?=?(ShowService)?enhancer.create();showService.sing("Mockingbird");} }通過以上實例可以看出,Cglib通過繼承實現動態代理,具體類不需要實現特定的接口,而且代理類可以調用具體類的非接口方法,更加靈活。
5.Spring AOP
5.1 概念
AOP的具體概念就不再說了,網上一搜一大把,這篇文章主要介紹Spring AOP低層使用的代理技術,因為平時在使用Spring AOP時,很多人都是copy配置,對上面介紹的這些技術概念并不清楚。
Spring AOP采用的是動態代理,在運行期間對業務方法進行增強,所以不會生成新類,對于動態代理技術,Spring AOP提供了對JDK動態代理的支持以及CGLib的支持,然而什么時候用哪種代理呢?
1、如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP
2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP
3、如果目標對象沒有實現了接口,必須采用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換
目前來看,Spring貌似和AspectJ沒半毛錢關系,那為什么在許多應用了Spring AOP的項目中都出現了@AspectJ的注解呢?Spring是應用的動態代理,怎么會還和AspectJ有關系呢,原因是Spring AOP基于注解配置的情況下,需要依賴于AspectJ包的標準注解,但是不需要額外的編譯以及AspectJ的織入器,而基于XML配置不需要,所以Spring AOP只是復用了AspectJ的注解,并沒有其他依賴AspectJ的地方。
當Spring需要使用@AspectJ注解支持時,需要在Spring配置文件中如下配置:
<aop:aspectj-autoproxy/>?而關于第二點強制使用CGLIB,可以通過在Spring的配置文件如下配置實現:
<aop:aspectj-autoproxy?proxy-target-class="true"/>?proxy-target-class屬性值決定是基于接口的還是基于類的代理被創建。如果proxy-target-class 屬性值被設置為true,那么基于類的代理將起作用(這時需要cglib庫)。如果proxy-target-class屬值被設置為false或者這個屬性被省略,那么標準的JDK 基于接口的代理。
所以,雖然使用了Aspect的Annotation,但是并沒有使用它的編譯器和織入器。其實現原理是JDK動態代理或Cglib,在運行時生成代理類。
已經寫了這么多了,下面再貼兩個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><!--?定義切點函數?--><aop:pointcut?id="singPointCut"?expression="execution(*?com.fufu.proxy.Star.sing(..))"/><!--?定義切面?order?定義優先級,值越小優先級越大--><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>測試類:
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>測試類:
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.總結
以上內容,雖然比較淺顯易懂,但是可以對Java代理機制和Spring AOP會有一個全面的理解,如有錯誤,歡迎指正。
總結
以上是生活随笔為你收集整理的从代理机制到Spring AOP,这篇给你安排的明明白白的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 亿级大表分库分表实战总结(万字干货,实战
- 下一篇: Java面试题阶段汇总