Spring AOP原理解析、CGLIB解析


Spring AOP原理解析
java

原文:http://blog.jobbole.com/28791/git


筆記:程序員


簡介: AOP(Aspect Orient Programming),也就是面向方面編程,做爲面向對象編程的一種補充,專門用於處理系統中分佈於各個模塊(不一樣方法)中的交叉關注點的問題,在 Java EE 應用中,經常經過 AOP 來處理一些具備橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等。AOP 實現的關鍵就在於 AOP 框架自動建立的 AOP 代理,AOP 代理主要分爲靜態代理和動態代理兩大類,靜態代理以 AspectJ 爲表明;而動態代理則以 Spring AOP 爲表明。本文會從 AspectJ 分析起,逐漸深刻,並介紹 CGLIB 來介紹 Spring AOP 框架的實現原理。github

AOP(Aspect Orient Programming),做爲面向對象編程的一種補充,普遍應用於處理一些具備橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等。AOP 實現的關鍵就在於 AOP 框架自動建立的 AOP 代理,AOP 代理則可分爲靜態代理和動態代理兩大類,其中靜態代理是指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,所以也稱爲編譯時加強;而動態代理則在運行時藉助於 JDK 動態代理、CGLIB 等在內存中「臨時」生成 AOP 動態代理類,所以也被稱爲運行時加強。編程

Spring AOP 實現原理與 CGLIB 應用


Spring AOP 原理剖析緩存

經過Spring AOP配置使用能夠知道:安全

AOP 代理實際上是由 AOP 框架動態生成的一個對象,該對象可做爲目標對象使用。AOP 代理包含了目標對象的所有方法,但 AOP 代理中的方法與目標對象的方法存在差別:AOP 方法在特定切入點添加了加強處理,並回調了目標對象的方法。bash

AOP 代理所包含的方法與目標對象的方法示意圖如圖 3 所示。框架

圖 3.AOP 代理的方法與目標對象的方法工具

Spring AOP 實現原理與 CGLIB 應用

 

Spring 的 AOP 代理由 Spring 的 IoC 容器負責生成、管理,其依賴關係也由 IoC 容器負責管理。所以,AOP 代理能夠直接使用容器中的其餘 Bean 實例做爲目標,這種關係可由 IoC 容器的依賴注入提供。

縱觀 AOP 編程,其中須要程序員參與的只有 3 個部分:

● 定義普通業務組件。

● 定義切入點,一個切入點可能橫切多個業務組件。

● 定義加強處理,加強處理就是在 AOP 框架爲普通業務組件織入的處理動做。

上面 3 個部分的第一個部分是最日常不過的事情,無須額外說明。那麼進行 AOP 編程的關鍵就是定義切入點和定義加強處理。一旦定義了合適的切入點和加強處理,AOP 框架將會自動生成 AOP 代理,而 AOP 代理的方法大體有以下公式:

代理對象的方法 = 加強處理 + 被代理對象的方法

在上面這個業務定義中,不難發現 Spring AOP 的實現原理其實很簡單:AOP 框架負責動態地生成 AOP 代理類,這個代理類的方法則由 Advice 和回調目標對象的方法所組成。

對於前面提到的圖 2 所示的軟件調用結構:當方法 一、方法 二、方法 3 ……都須要去調用某個具備「橫切」性質的方法時,傳統的作法是程序員去手動修改方法 一、方法 二、方法 3 ……、經過代碼來調用這個具備「橫切」性質的方法,但這種作法的可擴展性很差,由於每次都要改代碼。

因而 AOP 框架出現了,AOP 框架則能夠「動態的」生成一個新的代理類,而這個代理類所包含的方法 一、方法 二、方法 3 ……也增長了調用這個具備「橫切」性質的方法——但這種調用由 AOP 框架自動生成的代理類來負責,所以具備了極好的擴展性。程序員無需手動修改方法 一、方法 二、方法 3 的代碼,程序員只要定義切入點便可—— AOP 框架所生成的 AOP 代理類中包含了新的方法 一、訪法 二、方法 3,而 AOP 框架會根據切入點來決定是否要在方法 一、方法 二、方法 3 中回調具備「橫切」性質的方法。


簡而言之:AOP 原理的奧妙就在於動態地生成了代理類,這個代理類實現了圖 2 的調用——這種調用無需程序員修改代碼。


接下來介紹的 CGLIB 就是一個代理生成庫,下面介紹如何使用 CGLIB 來生成代理類。


使用 CGLIB 生成代理類

CGLIB(Code Generation Library),簡單來講,就是一個代碼生成類庫。它能夠在運行時候動態是生成某個類的子類。

此處使用前面定義的 Chinese 類,如今改成直接使用 CGLIB 來生成代理,這個代理類一樣能夠實現 Spring AOP 代理所達到的效果。

下面先爲 CGLIB 提供一個攔截器實現類:

清單 12.AroundAdvice.java

上面這個 AroundAdvice.java 的做用就像前面介紹的 Around Advice,它能夠在調用目標方法以前、調用目標方法以後織入加強處理。

接下來程序提供一個 ChineseProxyFactory 類,這個 ChineseProxyFactory 類會經過 CGLIB 來爲 Chinese 生成代理類:

清單 13.ChineseProxyFactory.java

上面粗體字代碼就是使用 CGLIB 的 Enhancer 生成代理對象的關鍵代碼,此時的 Enhancer 將以 Chinese 類做爲目標類,以 AroundAdvice 對象做爲「Advice」,程序將會生成一個 Chinese 的子類,這個子類就是 CGLIB 生成代理類,它可做爲 Chinese 對象使用,但它加強了 Chinese 類的方法。

測試 Chinese 代理類的主程序以下:

清單 14.Main.java

運行上面主程序,看到以下輸出結果:

執行目標方法以前,模擬開始事務 …

— 正在執行 sayHello 方法 —

執行目標方法以後,模擬結束事務 …

被改變的參數 Hello , CGLIB 新增的內容

執行目標方法以前,模擬開始事務 …

我正在吃 : 被改變的參數

執行目標方法以後,模擬結束事務 …

class lee.Chinese EnhancerByCGLIB 4bd097d9

從上面輸出結果來看,CGLIB 生成的代理徹底能夠做爲 Chinese 對象來使用,並且 CGLIB 代理對象的 sayHello()、eat() 兩個方法已經增長了事務控制(只是模擬),這個 CGLIB 代理其實就是 Spring AOP 所生成的 AOP 代理。

經過程序最後的輸出,不難發現這個代理對象的實現類是 lee.Chinese EnhancerByCGLIB 4bd097d9,這就是 CGLIB 所生成的代理類,這個代理類的格式與前面 Spring AOP 所生成的代理類的格式徹底相同。

這就是 Spring AOP 的根本所在:Spring AOP 就是經過 CGLIB 來動態地生成代理對象,這個代理對象就是所謂的 AOP 代理,而 AOP 代理的方法則經過在目標對象的切入點動態地織入加強處理,從而完成了對目標方法的加強。

小結

AOP 普遍應用於處理一些具備橫切性質的系統級服務,AOP 的出現是對 OOP 的良好補充,它使得開發者能用更優雅的方式處理具備橫切性質的服務。不論是那種 AOP 實現,不管是 AspectJ、仍是 Spring AOP,它們都須要動態地生成一個 AOP 代理類,區別只是生成 AOP 代理類的時機不一樣:AspectJ 採用編譯時生成 AOP 代理類,所以具備更好的性能,但須要使用特定的編譯器進行處理;而 Spring AOP 則採用運行時生成 AOP 代理類,所以無需使用特定編譯器進行處理。因爲 Spring AOP 須要在每次運行時生成 AOP 代理,所以性能略差一些。



CGLIB實現動態代理原理:

原文:http://blog.jobbole.com/105423/?utm_source=blog.jobbole.com&utm_medium=relatedPosts

jdk中的動態代理經過反射類ProxyInvocationHandler回調接口實現,要求委託類必須實現一個接口,只能對該類接口中定義的方法實現代理,這在實際編程中有必定的侷限性。

cglib實現

使用cglib[Code Generation Library]實現動態代理,並不要求委託類必須實現接口,底層採用asm字節碼生成框架生成代理類的字節碼,下面經過一個例子看看使用CGLib如何實現動態代理。
一、定義業務邏輯

public class UserServiceImpl { public void add() { System.out.println("This is add service"); } public void delete(int id) { System.out.println("This is delete service:delete " + id ); } }

二、實現MethodInterceptor接口,定義方法的攔截器

public class MyMethodInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable { System.out.println("Before:" + method); Object object = proxy.invokeSuper(obj, arg); System.out.println("After:" + method); return object; } }

三、利用Enhancer類生成代理類;

Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl.class); enhancer.setCallback(new MyMethodInterceptor()); UserServiceImpl userService = (UserServiceImpl)enhancer.create();

四、userService.add()的執行結果:

Before: add This is add service After: add

代理對象的生成過程由Enhancer類實現,大概步驟以下:
一、生成代理類Class的二進制字節碼;
二、經過Class.forName加載二進制字節碼,生成Class對象;
三、經過反射機制獲取實例構造,並初始化代理類對象。

cglib字節碼生成

Enhancer是CGLib的字節碼加強器,能夠方便的對類進行擴展,內部調用GeneratorStrategy.generate方法生成代理類的字節碼,經過如下方式能夠生成class文件。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\\\Code\\\\whywhy\\\\target\\\\classes\\\\zzzzzz")

使用 反編譯工具 procyon 查看代理類實現

java -jar procyon-decompiler-0.5.30.jar UserService$$EnhancerByCGLIB$$394dddeb;

反編譯以後的代理類add方法實現以下:

import net.sf.cglib.core.Signature;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;

// 
// Decompiled by Procyon v0.5.30 // public class UserService$$EnhancerByCGLIB$$394dddeb extends UserService implements Factory { private boolean CGLIB$BOUND; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; private MethodInterceptor CGLIB$CALLBACK_0; private static final Method CGLIB$add$0$Method; private static final MethodProxy CGLIB$add$0$Proxy; private static final Object[] CGLIB$emptyArgs; static void CGLIB$STATICHOOK2() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; final Class<?> forName = Class.forName("UserService$$EnhancerByCGLIB$$394dddeb"); final Class<?> forName3; CGLIB$add$0$Method = ReflectUtils.findMethods(new String[] { "add", "()V" }, (forName3 = Class.forName("UserService")).getDeclaredMethods())[0]; CGLIB$add$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "add", "CGLIB$add$0"); } final void CGLIB$add$0() { super.add(); } public final void add() { MethodInterceptor cglib$CALLBACK_2; MethodInterceptor cglib$CALLBACK_0; if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) { CGLIB$BIND_CALLBACKS(this); cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0); } if (cglib$CALLBACK_0 != null) { cglib$CALLBACK_2.intercept((Object)this, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Method, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$emptyArgs, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Proxy); return; } super.add(); } static { CGLIB$STATICHOOK2(); } }

經過cglib生成的字節碼相比jdk實現來講顯得更加複雜。
一、代理類UserService$$EnhancerByCGLIB$$394dddeb繼承了委託類UserSevice,且委託類的final方法不能被代理;
二、代理類爲每一個委託方法都生成兩個方法,以add方法爲例,一個是重寫的add方法,一個是CGLIB$add$0方法,該方法直接調用委託類的add方法;
三、當執行代理對象的add方法時,會先判斷是否存在實現了MethodInterceptor接口的對象cglib$CALLBACK_0,若是存在,則調用MethodInterceptor對象的intercept方法:

public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) { System.out.println("Before:" + method); Object object = proxy.invokeSuper(obj, arg); System.out.println("After:" + method); return object; }

參數分別爲:一、代理對象;二、委託類方法;三、方法參數;四、代理方法的MethodProxy對象。

四、每一個被代理的方法都對應一個MethodProxy對象,methodProxy.invokeSuper方法最終調用委託類的add方法,實現以下:

public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }

單看invokeSuper方法的實現,彷佛看不出委託類add方法調用,在MethodProxy實現中,經過FastClassInfo維護了委託類和代理類的FastClass。

private static class FastClassInfo { FastClass f1; FastClass f2; int i1; int i2; }

以add方法的methodProxy爲例,f1指向委託類對象,f2指向代理類對象,i1和i2分別是方法add和CGLIB$add$0在對象中索引位置。

FastClass實現機制

FastClass其實就是對Class對象進行特殊處理,提出下標概念index,經過索引保存方法的引用信息,將原先的反射調用,轉化爲方法的直接調用,從而體現所謂的fast,下面經過一個例子瞭解一下FastClass的實現機制。
一、定義原類

class Test { public void f(){ System.out.println("f method"); } public void g(){ System.out.println("g method"); } }

二、定義Fast類

class FastTest { public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } public Object invoke(int index, Object o, Object[] ol){ Test t = (Test) o; switch(index){ case 1: t.f(); return null; case 2: t.g(); return null; } return null; } }

在FastTest中有兩個方法,getIndex中對Test類的每一個方法根據hash創建索引,invoke根據指定的索引,直接調用目標方法,避免了反射調用。因此當調用methodProxy.invokeSuper方法時,其實是調用代理類的CGLIB$add$0方法,CGLIB$add$0直接調用了委託類的add方法。

jdk和cglib動態代理實現的區別

一、jdk動態代理生成的代理類和委託類實現了相同的接口; 二、cglib動態代理中生成的字節碼更加複雜,生成的代理類是委託類的子類,且不能處理被final關鍵字修飾的方法; 三、jdk採用反射機制調用委託類的方法,cglib採用相似索引的方式直接調用委託類方法;

相關文章
相關標籤/搜索