摘要:使用與的靜態代理不同,使用的動態代理,所謂的動態代理就是說框架不會去修改字節碼,而是在內存中臨時為方法生成一個對象,這個對象包含了目標對象的全部方法,并且在特定的切點做了增強處理,并回調原對象的方法。
AOP(Aspect Orient Programming),我們一般稱為面向方面(切面)編程,作為面向對象的一種補充,用于處理系統中分布于各個模塊的橫切關注點,比如事務管理、日志、緩存等等。AOP實現的關鍵在于AOP框架自動創建的AOP代理,AOP代理主要分為靜態代理和動態代理,靜態代理的代表為AspectJ;而動態代理則以Spring AOP為代表。本文會分別對AspectJ和Spring AOP的實現進行分析和介紹。
使用AspectJ的編譯時增強實現AOP之前提到,AspectJ是靜態代理的增強,所謂的靜態代理就是AOP框架會在編譯階段生成AOP代理類,因此也稱為編譯時增強。
舉個實例的例子來說。首先我們有一個普通的Hello類
public class Hello { public void sayHello() { System.out.println("hello"); } public static void main(String[] args) { Hello h = new Hello(); h.sayHello(); } }
使用AspectJ編寫一個Aspect
public aspect TxAspect { void around():call(void Hello.sayHello()){ System.out.println("開始事務 ..."); proceed(); System.out.println("事務結束 ..."); } }
這里模擬了一個事務的場景,類似于Spring的聲明式事務。使用AspectJ的編譯器編譯
ajc -d . Hello.java TxAspect.aj
編譯完成之后再運行這個Hello類,可以看到以下輸出
開始事務 ... hello 事務結束 ...
顯然,AOP已經生效了,那么究竟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); } }
可以看到,這個類比原來的Hello.java多了一些代碼,這就是AspectJ的靜態代理,它會在編譯階段將Aspect織入Java字節碼中,
運行的時候就是經過增強之后的AOP對象。
public void ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) { System.out.println("開始事務 ..."); ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983proceed(ajc$aroundClosure); System.out.println("事務結束 ..."); }
從Aspect編譯后的class文件可以更明顯的看出執行的邏輯。proceed方法就是回調執行被代理類中的方法。
使用Spring AOP與AspectJ的靜態代理不同,Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改字節碼,而是在內存中臨時為方法生成一個AOP對象,這個AOP對象包含了目標對象的全部方法,并且在特定的切點做了增強處理,并回調原對象的方法。
Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理通過反射來接收被代理的類,并且要求被代理的類必須實現一個接口。JDK動態代理的核心是InvocationHandler接口和Proxy類。
如果目標類沒有實現接口,那么Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態代理的。
為了驗證以上的說法,可以做一個簡單的測試。首先測試實現接口的情況。
定義一個接口
public interface Person { String sayHello(String name); }
實現類
@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注解是我自己定義的一個普通注解,用來標記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"); } }
運行
@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的動態代理。
再來看看不實現接口的情況,修改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); } }
運行
@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增強了,也就是動態代理。這里的CGLIB代理就是Spring AOP的代理,這個類也就是所謂的AOP代理,AOP代理類在切點動態地織入了增強處理。
小結AspectJ在編譯時就增強了目標對象,Spring AOP的動態代理則是在每次運行時動態的增強,生成AOP代理對象,區別在于生成AOP代理對象的時機不同,相對來說AspectJ的靜態代理方式具有更好的性能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。
參考:《Spring AOP 實現原理與 CGLIB 應用》
《Spring 容器AOP的實現原理——動態代理》
《AOP的底層實現-CGLIB動態代理和JDK動態代理》
我的個人博客原文。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/66098.html
摘要:在寫完容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了天時間閱讀了方面的源碼。從今天開始,我將對部分的源碼分析系列文章進行更新。全稱是,即面向切面的編程,是一種開發理念。在中,切面只是一個概念,并沒有一個具體的接口或類與此對應。 1. 簡介 前一段時間,我學習了 Spring IOC 容器方面的源碼,并寫了數篇文章對此進行講解。在寫完 Spring IOC 容器源碼分析系列...
摘要:總結動態代理的相關原理已經講解完畢,接下來讓我們回答以下幾個思考題。 【干貨點】 此處是【好好面試】系列文的第12篇文章。文章目標主要是通過原理剖析的方式解答Aop動態代理的面試熱點問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進而解決被面試官卡住喉嚨的情況。問題如下 SpringBoot默認代理類型是什么 為什么不用靜態代理 JDK動態代理原理 CGLIB動態代理...
摘要:,,面向切面編程。,切點,切面匹配連接點的點,一般與切點表達式相關,就是切面如何切點。例子中,注解就是切點表達式,匹配對應的連接點,通知,指在切面的某個特定的連接點上執行的動作。,織入,將作用在的過程。因為源碼都是英文寫的。 之前《零基礎帶你看Spring源碼——IOC控制反轉》詳細講了Spring容器的初始化和加載的原理,后面《你真的完全了解Java動態代理嗎?看這篇就夠了》介紹了下...
摘要:在上文中,我實現了一個很簡單的和容器。比如,我們所熟悉的就是在這里將切面邏輯織入相關中的。初始化的工作算是結束了,此時處于就緒狀態,等待外部程序的調用。其中動態代理只能代理實現了接口的對象,而動態代理則無此限制。 1. 背景 本文承接上文,來繼續說說 IOC 和 AOP 的仿寫。在上文中,我實現了一個很簡單的 IOC 和 AOP 容器。上文實現的 IOC 和 AOP 功能很單一,且 I...
摘要:由于的限制,無法替換被代理類已經被載入的字節碼,只能生成并載入一個新的子類作為代理類,被代理類的字節碼依然存在于中。區別于前兩者,是一種靜態代理的實現,即在編譯時或者載入類時直接修改被代理類文件的字節碼,而非運行時實時生成代理。 現象描述 上周同事發現其基于mySql實現的分布式鎖的線上代碼存在問題,代碼簡化如下: @Controller class XService { @A...
閱讀 3718·2021-11-25 09:43
閱讀 2606·2021-11-18 13:11
閱讀 2220·2019-08-30 15:55
閱讀 3277·2019-08-26 11:58
閱讀 2831·2019-08-26 10:47
閱讀 2235·2019-08-26 10:20
閱讀 1278·2019-08-23 17:59
閱讀 3014·2019-08-23 15:54