摘要:使用與的靜態(tài)代理不同,使用的動(dòng)態(tài)代理,所謂的動(dòng)態(tài)代理就是說框架不會(huì)去修改字節(jié)碼,而是在內(nèi)存中臨時(shí)為方法生成一個(gè)對(duì)象,這個(gè)對(duì)象包含了目標(biāo)對(duì)象的全部方法,并且在特定的切點(diǎn)做了增強(qiáng)處理,并回調(diào)原對(duì)象的方法。
AOP(Aspect Orient Programming),我們一般稱為面向方面(切面)編程,作為面向?qū)ο蟮囊环N補(bǔ)充,用于處理系統(tǒng)中分布于各個(gè)模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、日志、緩存等等。AOP實(shí)現(xiàn)的關(guān)鍵在于AOP框架自動(dòng)創(chuàng)建的AOP代理,AOP代理主要分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理的代表為AspectJ;而動(dòng)態(tài)代理則以Spring AOP為代表。本文會(huì)分別對(duì)AspectJ和Spring AOP的實(shí)現(xiàn)進(jìn)行分析和介紹。
使用AspectJ的編譯時(shí)增強(qiáng)實(shí)現(xiàn)AOP之前提到,AspectJ是靜態(tài)代理的增強(qiáng),所謂的靜態(tài)代理就是AOP框架會(huì)在編譯階段生成AOP代理類,因此也稱為編譯時(shí)增強(qiáng)。
舉個(gè)實(shí)例的例子來說。首先我們有一個(gè)普通的Hello類
public class Hello { public void sayHello() { System.out.println("hello"); } public static void main(String[] args) { Hello h = new Hello(); h.sayHello(); } }
使用AspectJ編寫一個(gè)Aspect
public aspect TxAspect { void around():call(void Hello.sayHello()){ System.out.println("開始事務(wù) ..."); proceed(); System.out.println("事務(wù)結(jié)束 ..."); } }
這里模擬了一個(gè)事務(wù)的場(chǎng)景,類似于Spring的聲明式事務(wù)。使用AspectJ的編譯器編譯
ajc -d . Hello.java TxAspect.aj
編譯完成之后再運(yùn)行這個(gè)Hello類,可以看到以下輸出
開始事務(wù) ... hello 事務(wù)結(jié)束 ...
顯然,AOP已經(jīng)生效了,那么究竟AspectJ是如何在沒有修改Hello類的情況下為Hello類增加新功能的呢?
查看一下編譯后的Hello.class
public class Hello { public Hello() { } public void sayHello() { System.out.println("hello"); } public static void main(String[] args) { Hello h = new Hello(); sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null); } }
可以看到,這個(gè)類比原來的Hello.java多了一些代碼,這就是AspectJ的靜態(tài)代理,它會(huì)在編譯階段將Aspect織入Java字節(jié)碼中,
運(yùn)行的時(shí)候就是經(jīng)過增強(qiáng)之后的AOP對(duì)象。
public void ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) { System.out.println("開始事務(wù) ..."); ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983proceed(ajc$aroundClosure); System.out.println("事務(wù)結(jié)束 ..."); }
從Aspect編譯后的class文件可以更明顯的看出執(zhí)行的邏輯。proceed方法就是回調(diào)執(zhí)行被代理類中的方法。
使用Spring AOP與AspectJ的靜態(tài)代理不同,Spring AOP使用的動(dòng)態(tài)代理,所謂的動(dòng)態(tài)代理就是說AOP框架不會(huì)去修改字節(jié)碼,而是在內(nèi)存中臨時(shí)為方法生成一個(gè)AOP對(duì)象,這個(gè)AOP對(duì)象包含了目標(biāo)對(duì)象的全部方法,并且在特定的切點(diǎn)做了增強(qiáng)處理,并回調(diào)原對(duì)象的方法。
Spring AOP中的動(dòng)態(tài)代理主要有兩種方式,JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。JDK動(dòng)態(tài)代理通過反射來接收被代理的類,并且要求被代理的類必須實(shí)現(xiàn)一個(gè)接口。JDK動(dòng)態(tài)代理的核心是InvocationHandler接口和Proxy類。
如果目標(biāo)類沒有實(shí)現(xiàn)接口,那么Spring AOP會(huì)選擇使用CGLIB來動(dòng)態(tài)代理目標(biāo)類。CGLIB(Code Generation Library),是一個(gè)代碼生成的類庫,可以在運(yùn)行時(shí)動(dòng)態(tài)的生成某個(gè)類的子類,注意,CGLIB是通過繼承的方式做的動(dòng)態(tài)代理,因此如果某個(gè)類被標(biāo)記為final,那么它是無法使用CGLIB做動(dòng)態(tài)代理的。
為了驗(yàn)證以上的說法,可以做一個(gè)簡(jiǎn)單的測(cè)試。首先測(cè)試實(shí)現(xiàn)接口的情況。
定義一個(gè)接口
public interface Person { String sayHello(String name); }
實(shí)現(xiàn)類
@Component public class Chinese implements Person { @Timer @Override public String sayHello(String name) { System.out.println("-- sayHello() --"); return name + " hello, AOP"; } public void eat(String food) { System.out.println("我正在吃:" + food); } }
這里的@Timer注解是我自己定義的一個(gè)普通注解,用來標(biāo)記Pointcut。
定義Aspect
@Aspect @Component public class AdviceTest { @Pointcut("@annotation(com.listenzhangbin.aop.Timer)") public void pointcut() { } @Before("pointcut()") public void before() { System.out.println("before"); } }
運(yùn)行
@SpringBootApplication @RestController public class SpringBootDemoApplication { //這里必須使用Person接口做注入 @Autowired private Person chinese; @RequestMapping("/test") public void test() { chinese.sayHello("listen"); System.out.println(chinese.getClass()); } public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
輸出
before -- sayHello() -- class com.sun.proxy.$Proxy53
可以看到類型是com.sun.proxy.$Proxy53,也就是前面提到的Proxy類,因此這里Spring AOP使用了JDK的動(dòng)態(tài)代理。
再來看看不實(shí)現(xiàn)接口的情況,修改Chinese類
@Component public class Chinese { @Timer // @Override public String sayHello(String name) { System.out.println("-- sayHello() --"); return name + " hello, AOP"; } public void eat(String food) { System.out.println("我正在吃:" + food); } }
運(yùn)行
@SpringBootApplication @RestController public class SpringBootDemoApplication { //直接用Chinese類注入 @Autowired private Chinese chinese; @RequestMapping("/test") public void test() { chinese.sayHello("listen"); System.out.println(chinese.getClass()); } public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
輸出
before -- sayHello() -- class com.listenzhangbin.aop.Chinese$$EnhancerBySpringCGLIB$$56b89168
可以看到類被CGLIB增強(qiáng)了,也就是動(dòng)態(tài)代理。這里的CGLIB代理就是Spring AOP的代理,這個(gè)類也就是所謂的AOP代理,AOP代理類在切點(diǎn)動(dòng)態(tài)地織入了增強(qiáng)處理。
小結(jié)AspectJ在編譯時(shí)就增強(qiáng)了目標(biāo)對(duì)象,Spring AOP的動(dòng)態(tài)代理則是在每次運(yùn)行時(shí)動(dòng)態(tài)的增強(qiáng),生成AOP代理對(duì)象,區(qū)別在于生成AOP代理對(duì)象的時(shí)機(jī)不同,相對(duì)來說AspectJ的靜態(tài)代理方式具有更好的性能,但是AspectJ需要特定的編譯器進(jìn)行處理,而Spring AOP則無需特定的編譯器處理。
參考:《Spring AOP 實(shí)現(xiàn)原理與 CGLIB 應(yīng)用》
《Spring 容器AOP的實(shí)現(xiàn)原理——?jiǎng)討B(tài)代理》
《AOP的底層實(shí)現(xiàn)-CGLIB動(dòng)態(tài)代理和JDK動(dòng)態(tài)代理》
我的個(gè)人博客原文。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/66098.html
摘要:在寫完容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了天時(shí)間閱讀了方面的源碼。從今天開始,我將對(duì)部分的源碼分析系列文章進(jìn)行更新。全稱是,即面向切面的編程,是一種開發(fā)理念。在中,切面只是一個(gè)概念,并沒有一個(gè)具體的接口或類與此對(duì)應(yīng)。 1. 簡(jiǎn)介 前一段時(shí)間,我學(xué)習(xí)了 Spring IOC 容器方面的源碼,并寫了數(shù)篇文章對(duì)此進(jìn)行講解。在寫完 Spring IOC 容器源碼分析系列...
摘要:總結(jié)動(dòng)態(tài)代理的相關(guān)原理已經(jīng)講解完畢,接下來讓我們回答以下幾個(gè)思考題。 【干貨點(diǎn)】 此處是【好好面試】系列文的第12篇文章。文章目標(biāo)主要是通過原理剖析的方式解答Aop動(dòng)態(tài)代理的面試熱點(diǎn)問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進(jìn)而解決被面試官卡住喉嚨的情況。問題如下 SpringBoot默認(rèn)代理類型是什么 為什么不用靜態(tài)代理 JDK動(dòng)態(tài)代理原理 CGLIB動(dòng)態(tài)代理...
摘要:,,面向切面編程。,切點(diǎn),切面匹配連接點(diǎn)的點(diǎn),一般與切點(diǎn)表達(dá)式相關(guān),就是切面如何切點(diǎn)。例子中,注解就是切點(diǎn)表達(dá)式,匹配對(duì)應(yīng)的連接點(diǎn),通知,指在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作。,織入,將作用在的過程。因?yàn)樵创a都是英文寫的。 之前《零基礎(chǔ)帶你看Spring源碼——IOC控制反轉(zhuǎn)》詳細(xì)講了Spring容器的初始化和加載的原理,后面《你真的完全了解Java動(dòng)態(tài)代理嗎?看這篇就夠了》介紹了下...
摘要:在上文中,我實(shí)現(xiàn)了一個(gè)很簡(jiǎn)單的和容器。比如,我們所熟悉的就是在這里將切面邏輯織入相關(guān)中的。初始化的工作算是結(jié)束了,此時(shí)處于就緒狀態(tài),等待外部程序的調(diào)用。其中動(dòng)態(tài)代理只能代理實(shí)現(xiàn)了接口的對(duì)象,而動(dòng)態(tài)代理則無此限制。 1. 背景 本文承接上文,來繼續(xù)說說 IOC 和 AOP 的仿寫。在上文中,我實(shí)現(xiàn)了一個(gè)很簡(jiǎn)單的 IOC 和 AOP 容器。上文實(shí)現(xiàn)的 IOC 和 AOP 功能很單一,且 I...
摘要:由于的限制,無法替換被代理類已經(jīng)被載入的字節(jié)碼,只能生成并載入一個(gè)新的子類作為代理類,被代理類的字節(jié)碼依然存在于中。區(qū)別于前兩者,是一種靜態(tài)代理的實(shí)現(xiàn),即在編譯時(shí)或者載入類時(shí)直接修改被代理類文件的字節(jié)碼,而非運(yùn)行時(shí)實(shí)時(shí)生成代理。 現(xiàn)象描述 上周同事發(fā)現(xiàn)其基于mySql實(shí)現(xiàn)的分布式鎖的線上代碼存在問題,代碼簡(jiǎn)化如下: @Controller class XService { @A...
閱讀 3684·2021-11-25 09:43
閱讀 2600·2021-11-18 13:11
閱讀 2195·2019-08-30 15:55
閱讀 3272·2019-08-26 11:58
閱讀 2823·2019-08-26 10:47
閱讀 2230·2019-08-26 10:20
閱讀 1271·2019-08-23 17:59
閱讀 2999·2019-08-23 15:54