生活随笔
收集整理的這篇文章主要介紹了
AOP的实现机制--转
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
原文地址:http://www.iteye.com/topic/1116696
1 AOP各種的實現(xiàn)
?
AOP就是面向切面編程,我們可以從幾個層面來實現(xiàn)AOP。
?
?
在編譯器修改源代碼,在運行期字節(jié)碼加載前修改字節(jié)碼或字節(jié)碼加載后動態(tài)創(chuàng)建代理類的字節(jié)碼,以下是各種實現(xiàn)機制的比較。?
?
| 類別 | 機制 | 原理 | 優(yōu)點 | 缺點 |
| 靜態(tài)AOP | 靜態(tài)織入 | 在編譯期,切面直接以字節(jié)碼的形式編譯到目標(biāo)字節(jié)碼文件中。 | 對系統(tǒng)無性能影響。 | 靈活性不夠。 |
| 動態(tài)AOP | 動態(tài)代理 | 在運行期,目標(biāo)類加載后,為接口動態(tài)生成代理類,將切面植入到代理類中。 | 相對于靜態(tài)AOP更加靈活。 | 切入的關(guān)注點需要實現(xiàn)接口。對系統(tǒng)有一點性能影響。 |
| 動態(tài)字節(jié)碼生成 | 在運行期,目標(biāo)類加載后,動態(tài)構(gòu)建字節(jié)碼文件生成目標(biāo)類的子類,將切面邏輯加入到子類中。 | 沒有接口也可以織入。 | 擴展類的實例方法為final時,則無法進行織入。 |
| 自定義類加載器 | 在運行期,目標(biāo)加載前,將切面邏輯加到目標(biāo)字節(jié)碼里。 | 可以對絕大部分類進行織入。 | 代碼中如果使用了其他類加載器,則這些類將不會被織入。 |
| 字節(jié)碼轉(zhuǎn)換 | 在運行期,所有類加載器加載字節(jié)碼前,前進行攔截。 | 可以對所有類進行織入。 | ? |
2 AOP里的公民??
- Joinpoint:攔截點,如某個業(yè)務(wù)方法。
- Pointcut:Joinpoint的表達式,表示攔截哪些方法。一個Pointcut對應(yīng)多個Joinpoint。
- Advice:? 要切入的邏輯。
- Before Advice 在方法前切入。
- After Advice 在方法后切入,拋出異常時也會切入。
- After Returning Advice 在方法返回后切入,拋出異常則不會切入。
- After Throwing Advice 在方法拋出異常時切入。
- Around Advice 在方法執(zhí)行前后切入,可以中斷或忽略原有流程的執(zhí)行。 ?
- 公民之間的關(guān)系?
織入器通過在切面中定義pointcut來搜索目標(biāo)(被代理類)的JoinPoint(切入點),然后把要切入的邏輯(Advice)織入到目標(biāo)對象里,生成代理類。?
3 AOP的實現(xiàn)機制?
? 本章節(jié)將詳細介紹AOP有各種實現(xiàn)機制。
3.1 動態(tài)代理?
? Java在JDK1.3后引入的動態(tài)代理機制,使我們可以在運行期動態(tài)的創(chuàng)建代理類。使用動態(tài)代理實現(xiàn)AOP需要有四個角色:被代理的類,被代理類的接口,織入器,和InvocationHandler,而織入器使用接口反射機制生成一個代理類,然后在這個代理類中織入代碼。被代理的類是AOP里所說的目標(biāo),InvocationHandler是切面,它包含了Advice和Pointcut。?
3.1.1 使用動態(tài)代理?
? 那如何使用動態(tài)代理來實現(xiàn)AOP。下面的例子演示在方法執(zhí)行前織入一段記錄日志的代碼,其中Business是代理類,LogInvocationHandler是記錄日志的切面,IBusiness, IBusiness2是代理類的接口,Proxy.newProxyInstance是織入器。?
清單一:動態(tài)代理的演示
Java代碼??
public?static?void?main(String[]?args)?{???????//需要代理的接口,被代理類實現(xiàn)的多個接口都必須在這里定義???????Class[]?proxyInterface?=?new?Class[]?{?IBusiness.class,?IBusiness2.class?};???????//構(gòu)建AOP的Advice,這里需要傳入業(yè)務(wù)類的實例???????LogInvocationHandler?handler?=?new?LogInvocationHandler(new?Business());???????//生成代理類的字節(jié)碼加載器???????ClassLoader?classLoader?=?DynamicProxyDemo.class.getClassLoader();???????//織入器,織入代碼并生成代理類???????IBusiness2?proxyBusiness?=?(IBusiness2)?Proxy.newProxyInstance(classLoader,?proxyInterface,?handler);???????//使用代理類的實例來調(diào)用方法。???????proxyBusiness.doSomeThing2();???????((IBusiness)?proxyBusiness).doSomeThing();???}?????/**??*?打印日志的切面??*/???public?static?class?LogInvocationHandler?implements?InvocationHandler?{?????????private?Object?target;?//目標(biāo)對象?????????LogInvocationHandler(Object?target)?{???????????this.target?=?target;???????}?????????@Override???????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{???????????//執(zhí)行原有邏輯???????????Object?rev?=?method.invoke(target,?args);???????????//執(zhí)行織入的日志,你可以控制哪些方法執(zhí)行切入邏輯???????????if?(method.getName().equals("doSomeThing2"))?{???????????????System.out.println("記錄日志");???????????}???????????return?rev;???????}???}?????接口IBusiness和IBusiness2定義省略。??? ?
?? 業(yè)務(wù)類,需要代理的類。
Java代碼??
public?class?Business?implements?IBusiness,?IBusiness2?{?????????@Override???????public?boolean?doSomeThing()?{???????????System.out.println("執(zhí)行業(yè)務(wù)邏輯");???????????return?true;???????}?????????@Override???????public?void?doSomeThing2()?{???????????System.out.println("執(zhí)行業(yè)務(wù)邏輯2");???????}?????}??? ?
?? 輸出
Java代碼??
執(zhí)行業(yè)務(wù)邏輯2???記錄日志???執(zhí)行業(yè)務(wù)邏輯??? ?
? 可以看到“記錄日志”的邏輯切入到Business類的doSomeThing方法前了。
?
3.1.2 動態(tài)代理原理?
??? 本節(jié)將結(jié)合動態(tài)代理的源代碼講解其實現(xiàn)原理。動態(tài)代理的核心其實就是代理對象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。讓我們進入newProxyInstance方法觀摩下,核心代碼其實就三行。?
清單二:生成代理類
Java代碼??
//獲取代理類???Class?cl?=?getProxyClass(loader,?interfaces);???//獲取帶有InvocationHandler參數(shù)的構(gòu)造方法???Constructor?cons?=?cl.getConstructor(constructorParams);???//把handler傳入構(gòu)造方法生成實例???return?(Object)?cons.newInstance(new?Object[]?{?h?});????? ?
??? 其中g(shù)etProxyClass(loader, interfaces)方法用于獲取代理類,它主要做了三件事情:在當(dāng)前類加載器的緩存里搜索是否有代理類,沒有則生成代理類并緩存在本地JVM里。清單三:查找代理類。
Java代碼??
?//?緩存的key使用接口名稱生成的List???Object?key?=?Arrays.asList(interfaceNames);???synchronized?(cache)?{???????do?{???Object?value?=?cache.get(key);????????????//?緩存里保存了代理類的引用???if?(value?instanceof?Reference)?{???????proxyClass?=?(Class)?((Reference)?value).get();???}???if?(proxyClass?!=?null)?{???//?代理類已經(jīng)存在則返回???????return?proxyClass;???}?else?if?(value?==?pendingGenerationMarker)?{???????//?如果代理類正在產(chǎn)生,則等待???????try?{???cache.wait();???????}?catch?(InterruptedException?e)?{???????}???????continue;???}?else?{???????//沒有代理類,則標(biāo)記代理準(zhǔn)備生成???????cache.put(key,?pendingGenerationMarker);???????break;???}???????}?while?(true);???}??? ??
代理類的生成主要是以下這兩行代碼。 清單四:生成并加載代理類
?
Java代碼??
//生成代理類的字節(jié)碼文件并保存到硬盤中(默認(rèn)不保存到硬盤)???proxyClassFile?=?ProxyGenerator.generateProxyClass(proxyName,?interfaces);???//使用類加載器將字節(jié)碼加載到內(nèi)存中???proxyClass?=?defineClass0(loader,?proxyName,proxyClassFile,?0,?proxyClassFile.length);??? ?
? ProxyGenerator.generateProxyClass()方法屬于sun.misc包下,Oracle并沒有提供源代碼,但是我們可以使用JD-GUI這樣的反編譯軟件打開jre\lib\rt.jar來一探究竟,以下是其核心代碼的分析。?
清單五:代理類的生成過程
Java代碼??
//添加接口中定義的方法,此時方法體為空???for?(int?i?=?0;?i?<?this.interfaces.length;?i++)?{?????localObject1?=?this.interfaces[i].getMethods();?????for?(int?k?=?0;?k?<?localObject1.length;?k++)?{????????addProxyMethod(localObject1[k],?this.interfaces[i]);?????}???}?????//添加一個帶有InvocationHandler的構(gòu)造方法???MethodInfo?localMethodInfo?=?new?MethodInfo("<init>",?"(Ljava/lang/reflect/InvocationHandler;)V",?1);?????//循環(huán)生成方法體代碼(省略)???//方法體里生成調(diào)用InvocationHandler的invoke方法代碼。(此處有所省略)???this.cp.getInterfaceMethodRef("InvocationHandler",?"invoke",?"Object;?Method;?Object;")?????//將生成的字節(jié)碼,寫入硬盤,前面有個if判斷,默認(rèn)情況下不保存到硬盤。???localFileOutputStream?=?new?FileOutputStream(ProxyGenerator.access$000(this.val$name)?+?".class");???localFileOutputStream.write(this.val$classFile);??? ?
? 那么通過以上分析,我們可以推出動態(tài)代理為我們生成了一個這樣的代理類。把方法doSomeThing的方法體修改為調(diào)用LogInvocationHandler的invoke方法。?
清單六:生成的代理類源碼
?
Java代碼??
public?class?ProxyBusiness?implements?IBusiness,?IBusiness2?{?????private?LogInvocationHandler?h;?????@Override???public?void?doSomeThing2()?{???????try?{???????????Method?m?=?(h.target).getClass().getMethod("doSomeThing",?null);???????????h.invoke(this,?m,?null);???????}?catch?(Throwable?e)?{???????????//?異常處理(略)???????}???}?????@Override???public?boolean?doSomeThing()?{???????try?{??????????Method?m?=?(h.target).getClass().getMethod("doSomeThing2",?null);??????????return?(Boolean)?h.invoke(this,?m,?null);???????}?catch?(Throwable?e)?{???????????//?異常處理(略)???????}???????return?false;???}?????public?ProxyBusiness(LogInvocationHandler?h)?{???????this.h?=?h;???}?????//測試用???public?static?void?main(String[]?args)?{???????//構(gòu)建AOP的Advice???????LogInvocationHandler?handler?=?new?LogInvocationHandler(new?Business());???????new?ProxyBusiness(handler).doSomeThing();???????new?ProxyBusiness(handler).doSomeThing2();???}???}??? ?
3.1.3 小結(jié)?
??? 從前兩節(jié)的分析我們可以看出,動態(tài)代理在運行期通過接口動態(tài)生成代理類,這為其帶來了一定的靈活性,但這個靈活性卻帶來了兩個問題,第一代理類必須實現(xiàn)一個接口,如果沒實現(xiàn)接口會拋出一個異常。第二性能影響,因為動態(tài)代理使用反射的機制實現(xiàn)的,首先反射肯定比直接調(diào)用要慢,經(jīng)過測試大概每個代理類比靜態(tài)代理多出10幾毫秒的消耗。其次使用反射大量生成類文件可能引起Full GC造成性能影響,因為字節(jié)碼文件加載后會存放在JVM運行時區(qū)的方法區(qū)(或者叫持久代)中,當(dāng)方法區(qū)滿的時候,會引起Full GC,所以當(dāng)你大量使用動態(tài)代理時,可以將持久代設(shè)置大一些,減少Full GC次數(shù)。?
3.2 動態(tài)字節(jié)碼生成?
?? 使用動態(tài)字節(jié)碼生成技術(shù)實現(xiàn)AOP原理是在運行期間目標(biāo)字節(jié)碼加載后,生成目標(biāo)類的子類,將切面邏輯加入到子類中,所以使用Cglib實現(xiàn)AOP不需要基于接口。
?
??? 本節(jié)介紹如何使用Cglib來實現(xiàn)動態(tài)字節(jié)碼技術(shù)。Cglib是一個強大的,高性能的Code生成類庫,它可以在運行期間擴展Java類和實現(xiàn)Java接口,它封裝了Asm,所以使用Cglib前需要引入Asm的jar。 清單七:使用CGLib實現(xiàn)AOP
Java代碼??
public?static?void?main(String[]?args)?{???????????byteCodeGe();???????}?????????public?static?void?byteCodeGe()?{???????????//創(chuàng)建一個織入器???????????Enhancer?enhancer?=?new?Enhancer();???????????//設(shè)置父類???????????enhancer.setSuperclass(Business.class);???????????//設(shè)置需要織入的邏輯???????????enhancer.setCallback(new?LogIntercept());???????????//使用織入器創(chuàng)建子類???????????IBusiness2?newBusiness?=?(IBusiness2)?enhancer.create();???????????newBusiness.doSomeThing2();???????}?????????/**???????*?記錄日志???????*/???????public?static?class?LogIntercept?implements?MethodInterceptor?{?????????????@Override???????????public?Object?intercept(Object?target,?Method?method,?Object[]?args,?MethodProxy?proxy)?throws?Throwable?{???????????????//執(zhí)行原有邏輯,注意這里是invokeSuper???????????????Object?rev?=?proxy.invokeSuper(target,?args);???????????????//執(zhí)行織入的日志???????????????if?(method.getName().equals("doSomeThing2"))?{???????????????????System.out.println("記錄日志");???????????????}???????????????return?rev;???????????}???????}??? ?
?
3.3 自定義類加載器?
?? 如果我們實現(xiàn)了一個自定義類加載器,在類加載到JVM之前直接修改某些類的方法,并將切入邏輯織入到這個方法里,然后將修改后的字節(jié)碼文件交給虛擬機運行,那豈不是更直接。
?
Javassist是一個編輯字節(jié)碼的框架,可以讓你很簡單地操作字節(jié)碼。它可以在運行期定義或修改Class。使用Javassist實現(xiàn)AOP的原理是在字節(jié)碼加載前直接修改需要切入的方法。這比使用Cglib實現(xiàn)AOP更加高效,并且沒太多限制,實現(xiàn)原理如下圖:?
??? 我們使用系統(tǒng)類加載器啟動我們自定義的類加載器,在這個類加載器里加一個類加載監(jiān)聽器,監(jiān)聽器發(fā)現(xiàn)目標(biāo)類被加載時就織入切入邏輯,咱們再看看使用Javassist實現(xiàn)AOP的代碼:?
清單八:啟動自定義的類加載器
Java代碼??
//獲取存放CtClass的容器ClassPool???ClassPool?cp?=?ClassPool.getDefault();???//創(chuàng)建一個類加載器???Loader?cl?=?new?Loader();???//增加一個轉(zhuǎn)換器???cl.addTranslator(cp,?new?MyTranslator());???//啟動MyTranslator的main函數(shù)???cl.run("jsvassist.JavassistAopDemo$MyTranslator",?args);??? ?清單九:類加載監(jiān)聽器
Java代碼??
public?static?class?MyTranslator?implements?Translator?{?????????????public?void?start(ClassPool?pool)?throws?NotFoundException,?CannotCompileException?{???????????}?????????????/*?*???????????*?類裝載到JVM前進行代碼織入???????????*/???????????public?void?onLoad(ClassPool?pool,?String?classname)?{???????????????if?(!"model$Business".equals(classname))?{???????????????????return;???????????????}???????????????//通過獲取類文件???????????????try?{???????????????????CtClass??cc?=?pool.get(classname);???????????????????//獲得指定方法名的方法???????????????????CtMethod?m?=?cc.getDeclaredMethod("doSomeThing");???????????????????//在方法執(zhí)行前插入代碼???????????????????m.insertBefore("{?System.out.println(\"記錄日志\");?}");???????????????}?catch?(NotFoundException?e)?{???????????????}?catch?(CannotCompileException?e)?{???????????????}???????????}?????????????public?static?void?main(String[]?args)?{???????????????Business?b?=?new?Business();???????????????b.doSomeThing2();???????????????b.doSomeThing();???????????}???????}??? ?輸出:?
Java代碼??
執(zhí)行業(yè)務(wù)邏輯2???記錄日志???執(zhí)行業(yè)務(wù)邏輯?? ??
??? 其中Bussiness類在本文的清單一中定義。看起來是不是特別簡單,CtClass是一個class文件的抽象描述。咱們也可以使用insertAfter()在方法的末尾插入代碼,使用insertAt()在指定行插入代碼。?
3.3.1 小結(jié)?
??? 從本節(jié)中可知,使用自定義的類加載器實現(xiàn)AOP在性能上要優(yōu)于動態(tài)代理和Cglib,因為它不會產(chǎn)生新類,但是它仍然存在一個問題,就是如果其他的類加載器來加載類的話,這些類將不會被攔截。?
3.4 字節(jié)碼轉(zhuǎn)換?
??? 自定義的類加載器實現(xiàn)AOP只能攔截自己加載的字節(jié)碼,那么有沒有一種方式能夠監(jiān)控所有類加載器加載字節(jié)碼呢?有,使用Instrumentation,它是 Java 5 提供的新特性,使用 Instrumentation,開發(fā)者可以構(gòu)建一個字節(jié)碼轉(zhuǎn)換器,在字節(jié)碼加載前進行轉(zhuǎn)換。本節(jié)使用Instrumentation和javassist來實現(xiàn)AOP。?
3.4.1 構(gòu)建字節(jié)碼轉(zhuǎn)換器?
??? 首先需要創(chuàng)建字節(jié)碼轉(zhuǎn)換器,該轉(zhuǎn)換器負(fù)責(zé)攔截Business類,并在Business類的doSomeThing方法前使用javassist加入記錄日志的代碼。
Java代碼??
public?class?MyClassFileTransformer?implements?ClassFileTransformer?{?????????/**???????*?字節(jié)碼加載到虛擬機前會進入這個方法???????*/???????@Override???????public?byte[]?transform(ClassLoader?loader,?String?className,?Class<?>?classBeingRedefined,???????????????????????????????ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)???????????????throws?IllegalClassFormatException?{???????????System.out.println(className);???????????//如果加載Business類才攔截???????????if?(!"model/Business".equals(className))?{???????????????return?null;???????????}?????????????//javassist的包名是用點分割的,需要轉(zhuǎn)換下???????????if?(className.indexOf("/")?!=?-1)?{???????????????className?=?className.replaceAll("/",?".");???????????}???????????try?{???????????????//通過包名獲取類文件???????????????CtClass?cc?=?ClassPool.getDefault().get(className);???????????????//獲得指定方法名的方法???????????????CtMethod?m?=?cc.getDeclaredMethod("doSomeThing");???????????????//在方法執(zhí)行前插入代碼???????????????m.insertBefore("{?System.out.println(\"記錄日志\");?}");???????????????return?cc.toBytecode();???????????}?catch?(NotFoundException?e)?{???????????}?catch?(CannotCompileException?e)?{???????????}?catch?(IOException?e)?{???????????????//忽略異常處理???????????}???????????return?null;???}??? ?
3.4.2 注冊轉(zhuǎn)換器?
??? 使用premain函數(shù)注冊字節(jié)碼轉(zhuǎn)換器,該方法在main函數(shù)之前執(zhí)行。
Java代碼??
public?class?MyClassFileTransformer?implements?ClassFileTransformer?{???????public?static?void?premain(String?options,?Instrumentation?ins)?{???????????//注冊我自己的字節(jié)碼轉(zhuǎn)換器???????????ins.addTransformer(new?MyClassFileTransformer());???}???}??? ?
3.4.3 配置和執(zhí)行?
??? 需要告訴JVM在啟動main函數(shù)之前,需要先執(zhí)行premain函數(shù)。首先需要將premain函數(shù)所在的類打成jar包。并修改該jar包里的META-INF\MANIFEST.MF 文件。?
Java代碼??
Manifest-Version:?1.0???Premain-Class:?bci.?MyClassFileTransformer?? ???? 然后在JVM的啟動參數(shù)里加上。-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar?
???????????? 3.4.4 輸出
????執(zhí)行main函數(shù),你會發(fā)現(xiàn)切入的代碼無侵入性的織入進去了。
Java代碼??
public?static?void?main(String[]?args)?{??????new?Business().doSomeThing();??????new?Business().doSomeThing2();???}?????? ?? 輸出
Java代碼??
model/Business???sun/misc/Cleaner???java/lang/Enum???model/IBusiness???model/IBusiness2???記錄日志???執(zhí)行業(yè)務(wù)邏輯???執(zhí)行業(yè)務(wù)邏輯2???java/lang/Shutdown???java/lang/Shutdown$Lock??? ??
?從輸出中可以看到系統(tǒng)類加載器加載的類也經(jīng)過了這里。
?
4 AOP實戰(zhàn)?
說了這么多理論,那AOP到底能做什么呢? AOP能做的事情非常多。
- 性能監(jiān)控,在方法調(diào)用前后記錄調(diào)用時間,方法執(zhí)行太長或超時報警。
- 緩存代理,緩存某方法的返回值,下次執(zhí)行該方法時,直接從緩存里獲取。
- 軟件破解,使用AOP修改軟件的驗證類的判斷邏輯。
- 記錄日志,在方法執(zhí)行前后記錄系統(tǒng)日志。
- 工作流系統(tǒng),工作流系統(tǒng)需要將業(yè)務(wù)代碼和流程引擎代碼混合在一起執(zhí)行,那么我們可以使用AOP將其分離,并動態(tài)掛接業(yè)務(wù)。
- 權(quán)限驗證,方法執(zhí)行前驗證是否有權(quán)限執(zhí)行當(dāng)前方法,沒有則拋出沒有權(quán)限執(zhí)行異常,由業(yè)務(wù)代碼捕捉。?
4.1 Spring的AOP?
??? Spring默認(rèn)采取的動態(tài)代理機制實現(xiàn)AOP,當(dāng)動態(tài)代理不可用時(代理類無接口)會使用CGlib機制。但Spring的AOP有一定的缺點,第一個只能對方法進行切入,不能對接口,字段,靜態(tài)代碼塊進行切入(切入接口的某個方法,則該接口下所有實現(xiàn)類的該方法將被切入)。第二個同類中的互相調(diào)用方法將不會使用代理類。因為要使用代理類必須從Spring容器中獲取Bean。第三個性能不是最好的,從3.3章節(jié)我們得知使用自定義類加載器,性能要優(yōu)于動態(tài)代理和CGlib。?
可以獲取代理類
Java代碼??
public?IMsgFilterService?getThis()???{???????????return?(IMsgFilterService)?AopContext.currentProxy();???}?????public?boolean?evaluateMsg?()?{??????//?執(zhí)行此方法將織入切入邏輯???return?getThis().evaluateMsg(String?message);???}?????@MethodInvokeTimesMonitor("KEY_FILTER_NUM")???public?boolean?evaluateMsg(String?message)?{??? ?不能獲取代理類
Java代碼??
public?boolean?evaluateMsg?()?{??????//?執(zhí)行此方法將不會織入切入邏輯???return?evaluateMsg(String?message);???}?????@MethodInvokeTimesMonitor("KEY_FILTER_NUM")???public?boolean?evaluateMsg(String?message)?{??? ?
?4.2 參考資料
- Java 動態(tài)代理機制分析及擴展
- CGlib的官方網(wǎng)站
- ASM官方網(wǎng)站
- JbossAOP
- Java5特性Instrumenttation實踐?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/5732607.html
總結(jié)
以上是生活随笔為你收集整理的AOP的实现机制--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。