日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

AOP之@AspectJ技术原理详解

發(fā)布時(shí)間:2023/12/20 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 AOP之@AspectJ技术原理详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  • 一AOP
    • 1 主要功能
    • 2 主要目標(biāo)
    • 3 適用對象
    • 4 AOP與OOP的關(guān)系
  • 二Android中使用AspectJ
    • 1 Gradle 配置示
    • 2 基本概念
      • 21 切面Aspect
      • 22 連接點(diǎn)JoinPoint
      • 23 切點(diǎn)PointCut
      • 24 通知Advise
    • 3 執(zhí)原
      • 31 BeforeAfterAfterThrowing插入示意圖
      • 32 Around替換邏輯示意圖
      • 33 代碼分析
    • 4 AspectJ切面編寫
      • 41 日志打印
      • 42 耗時(shí)監(jiān)控
      • 43 異常處
      • 44 降級替代方案吐司
      • 45 其他的系統(tǒng)橫切關(guān)注點(diǎn)問題
  • 三相關(guān)問題
    • 1 編織速度
    • 2 調(diào)試工具


一、AOP

AOP是OOP的延續(xù),是軟件開發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯
各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開發(fā)的效率。

1.1 主要功能

日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等。

1.2 主要目標(biāo)

將日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨(dú)立到非指導(dǎo)業(yè)務(wù)邏輯的方法中,進(jìn)而改變
這些行為的時(shí)候不影響業(yè)務(wù)邏輯的代碼。

1.3 適用對象

比較大型的項(xiàng)目,而且迭代較快,使用OOP太消耗內(nèi)力。
有日志、性能、安全、異常處理等橫切關(guān)注點(diǎn)需求。

1.4 AOP與OOP的關(guān)系

OOP(面向?qū)ο缶幊?#xff09;針對業(yè)務(wù)處理過程的實(shí)體及其屬性和行為進(jìn)行抽象封裝,以獲得更加清晰高效的邏輯單元?jiǎng)澐帧5且灿兴娜秉c(diǎn),最明顯的就是關(guān)注點(diǎn)聚焦時(shí),面向?qū)ο鬅o法簡單的解決這個(gè)問題,一個(gè)關(guān)注點(diǎn)是面向所有而不是單一的類,不受類的邊界的約束,因此OOP無法將關(guān)注點(diǎn)聚焦來解決,只能分散到各個(gè)類中。
AOP(面向切面編程)則是針對業(yè)務(wù)處理過程中的切面進(jìn)行提取,它所面對的是處理過程中的某個(gè)步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。這兩種設(shè)計(jì)思想在目標(biāo)上有著本質(zhì)的差異。
AOP并不是與OOP對立的,而是為了彌補(bǔ)OOP的不足。OOP解決了豎向的問題,AOP則解決橫向的問題。因?yàn)橛辛薃OP我們的調(diào)試和監(jiān)控就變得簡單清晰。它們之間的關(guān)系如下圖所示:

1.4.1 對比一——單一橫切關(guān)注點(diǎn)

1.4.2 對比二——多橫切關(guān)注點(diǎn)

結(jié)論:


二、Android中使用@AspectJ

AspectJ 意思就是Java的Aspect,Java的AOP。它其實(shí)不是一個(gè)新的語言,它的核心是ajc(編譯?)\weaver(織入?)。

  • ajc編譯?:基于Java編譯?之上的,它是用來編譯.aj文件,aspectj在Java編譯?的基礎(chǔ)上增加了一些它自己的關(guān)鍵字和方法。因此,ajc也可以編譯Java代碼。
  • weaver織入?:為了在java編譯?上使用AspectJ而不依賴于Ajc編譯?,aspectJ 5出現(xiàn)了@AspectJ,使用注釋的方式編寫AspectJ代碼,可以在任何Java編譯?上使用。

由于AndroidStudio默認(rèn)是沒有ajc編譯?的,所以在Android中使用@AspectJ來編寫(包括SpringAOP也是如此)。它在代碼的編譯期間掃描目標(biāo)程序,根據(jù)切點(diǎn)(PointCut)匹配,將開發(fā)者編寫的Aspect程序編織(Weave)到目標(biāo)程序的.class文件中,對目標(biāo)程序作了重構(gòu)(重構(gòu)單位是JoinPoint),目的就是建立目標(biāo)程序與Aspect程序的連接(獲得執(zhí)行的對象、方法、參數(shù)等上下文信息),從而達(dá)到AOP的目的。

2.1 Gradle 配置示例

要引入AspectJ到Android工程中,最重要的就是兩個(gè)包:

//在buildscript中添加該編織?,gradle構(gòu)建時(shí)就會對class文件進(jìn)行編織 classpath 'org.aspectj:aspectjweaver:1.8.9' //在dependencies中添加該依賴,提供@AspectJ語法 compile 'org.aspectj:aspectjrt:1.8.9'

此外還有一個(gè)工具包,用于Gradle構(gòu)建時(shí)進(jìn)行打日志等操作:

//在buildscript中添加該工具包,在構(gòu)建工程的時(shí)候執(zhí)行一些任務(wù):打日志等 classpath 'org.aspectj:aspectjtools:1.8.9'import com.android.build.gradle.LibraryPlugin import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main//打印gradle日志 android.libraryVariants.all { variant -> LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.5","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]MessageHandler handler = new MessageHandler(true)?new Main().run(args, handler)def log = project.loggerfor (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak?case IMessage.WARNING:case IMessage.INFO:log.info message.message, message.thrownbreak?case IMessage.DEBUG:log.debug message.message, message.thrownbreak?}} } }

附:美團(tuán)RoboAspectJ

buildscript { repositories { mavenLocal() } dependencies { classpath 'com.meituan.gradle:roboaspectj:0.9.2' classpath 'jaop.gradle.plugin:gradle-plugin:1.0.2' } // Exclude the version that the android plugin depends on. configurations.classpath.exclude group: 'com.android.tools.external.lombok' }

配置參數(shù)

// AspectJ aspectj { disableWhenDebug true javartNeeded true // 排除不需要AOP掃描的包 exclude group: 'xxxx', module: 'xxxx' compileOptions { defaultJavaVersion = JavaVersion.VERSION_1_7 } }

2.2 基本概念

2.2.1 切面——Aspect

實(shí)現(xiàn)了cross-cutting功能,是針對切面的模塊。最常見的是logging模塊、方法執(zhí)行耗時(shí)模塊,這樣,程序按功能被分為好幾層,如果按傳統(tǒng)的繼承的話,商業(yè)模型繼承日志模塊的話需要插入修改的地方太多,而通過創(chuàng)建一個(gè)切面就可以使用AOP來實(shí)現(xiàn)相同的功能了,我們可以針對不同的需求做出不同的切面。

2.2.2 連接點(diǎn)——JoinPoint

連接點(diǎn)是切面插入應(yīng)用程序的地方,該點(diǎn)能被方法調(diào)用,而且也會被拋出意外。連接點(diǎn)是應(yīng)用程序提供給切面插入的地方,可以添加新的方法。比如:我們的切點(diǎn)可以認(rèn)為是findInfo(String)方法。
AspectJ將面向?qū)ο蟮某绦驁?zhí)行流程看成是JoinPoint的執(zhí)行鏈,每一個(gè)JoinPoint是一個(gè)單獨(dú)的閉包,在執(zhí)行的時(shí)候?qū)⑸舷挛沫h(huán)境賦予閉包執(zhí)行方法體邏輯。
下面列表上的是被AspectJ認(rèn)為是joinpoint的:

2.2.3 切點(diǎn)——PointCut

切點(diǎn)的聲明決定需要切割的JoinPoint的集合,就結(jié)果上來說,它是JoinPoint的一個(gè)實(shí)際子集合。
pointcut可以控制你把哪些advice應(yīng)用于jointpoint上去,通常通過正則表達(dá)式來進(jìn)行匹配應(yīng)用,決定了那個(gè)jointpoint會獲得通知。分為call、execution、target、this、within等關(guān)鍵字,含義下面會附圖。
1.直接針對JoinPoint的選擇
pointcuts中最常用的選擇條件和Joinpoint的類型密切相關(guān),比如圖5:

2.間接針對JPoint的選擇
除了根據(jù)前面提到的Signature信息來匹配JPoint外,AspectJ還提供其他一些選擇方法來選擇JPoint。比如某個(gè)類中的所有JPoint,每一個(gè)函數(shù)執(zhí)行流程中所包含的JPoint。
特別強(qiáng)調(diào),不論什么選擇方法,最終都是為了找到目標(biāo)的JPoint。
表2列出了一些常用的非JPoint選擇方法:

3.匹配規(guī)則
(1)類型匹配語法
首先讓我們來了解下AspectJ類型匹配的通配符:

*:匹配任何數(shù)量字符;
..:匹配任何數(shù)量字符的重復(fù),如在類型模式中匹配任何數(shù)量子包;而在方法參數(shù)模式中匹配任何數(shù)量參數(shù)。
+:匹配指定類型的子類型;僅能作為后綴放在類型模式后邊。
AspectJ使用 且(&&)、或(||)、非(!)來組合切入點(diǎn)表達(dá)式。

(2)匹配模式
call(<注解?> <修飾符?> <返回值類型> <類型聲明?>.<方法名>(參數(shù)列表) <異常列表>?)

  • 精確匹配
//表示匹配 com.davidkuper.MainActivity類中所有被@Describe注解的public void方法。 @Pointcut("call(@Describe public void com.davidkuper.MainActivity.init(Context))") public void pointCut(){}
  • 單一模糊匹配
//表示匹配 com.davidkuper.MainActivity類中所有被@Describe注解的public void方法。 @Pointcut("call(@Describe public void com.davidkuper.MainActivity.*(..)) ") public void pointCut(){} //表示匹配調(diào)用Toast及其子類調(diào)用的show方法,不論返回類型以及參數(shù)列表,并且該子類在以com.meituan或者com.sankuai開頭的包名內(nèi) @Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.meituan..*)|| within(com.sankuai..*))") public void toastShow() { }
  • 組合模糊匹配
//表示匹配任意Activity或者其子類的onStart方法執(zhí)行,不論返回類型以及參數(shù)列表,且該類在com.meituan.hotel.roadmap包名內(nèi) @Pointcut("execution(* *..Activity+.onStart(..))&& within(com.meituan.hotel.roadmap.*)") public void onStart(){}

(3)獲取參數(shù)

  • 通過聲明參數(shù)語法arg()顯示獲取參數(shù)
@Around(value = "execution(* BitmapFacade.picasso.init(java.lang.String,java.lang.String)) && args(arg1,arg2)" public Object aroundArgs(String arg1,String arg2,ProceedingJoinPoint joinPoint){System.out.println("aspects arg = " + arg1.toString()+" " + arg2)?Object resutObject = null?try {resutObject = joinPoint.proceed(new Object[]{arg1,arg2})?} catch (Throwable e) {e.printStackTrace()?}return resutObject? }
  • 通過joinPoint.getArg()獲取參數(shù)列表
@Around("execution(static * tBitmapFacade.picasso.init(..)) && !within(aspectj.*) ") public void pointCutAround(ProceedingJoinPoint joinPoint){Object resutObject = null?try {//獲取參數(shù)列表Object[] args = joinPoint.getArgs()?resutObject = joinPoint.proceed(args)?} catch (Throwable e) {e.printStackTrace()?}return resutObject? }?

(4)異常匹配

/** * 截獲Exception及其子類報(bào)出的異常。 * @param e 異常參數(shù) */ @Pointcut("handler(java.lang.Exception+)&&args(e)") public void handle(Exception e) {}

2.2.4 通知——Advise

advice是我們切面功能的實(shí)現(xiàn),它是切點(diǎn)的真正執(zhí)行的地方。比如像寫日志到一個(gè)文件中,會在pointcut匹配到的連接點(diǎn)中插入advice(包括:before、after、around等)代碼到應(yīng)用程序中。
(1)@Before、@After

//所有實(shí)例方法調(diào)用截獲 private static final String INSTANCE_METHOD_CALL = "call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)"? @Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() { } //實(shí)例方法調(diào)用前后Advice @Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) { printLog(joinPoint, "before instance call")? } @After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) { printLog(joinPoint, "after instance call")? }

(2)@Around

//橫切項(xiàng)目中所有Activity的子類,以Layout命名、以及它的子類的所有方法的執(zhí)行 private static final String POINTCUT_METHOD = "(execution(* android.app.Activity+.*(..)) ||execution(* *..Layout+.*(..)))&& within(com.meituan.hotel.roadmap.*)"? @Pointcut(POINTCUT_METHOD) public void methodAnnotated() { }@Around("methodAnnotated()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint)throws Throwable{//調(diào)用原方法的執(zhí)行。Object result = joinPoint.proceed()?return result? }

(3)@AfterThrowing

/** * 在異常拋出后,該操作優(yōu)先于下一個(gè)切點(diǎn)的@Before() * @param joinPoint * @param e 異常參數(shù) */ @AfterThrowing(pointcut = "afterThrow()",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Exception e){ Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " afterThrowing() :" + e.toString())? }

2.3 執(zhí)行原理

AspectJ是通過對目標(biāo)工程的.class文件進(jìn)行代碼注入的方式將通知(Advise)插入到目標(biāo)代碼中。
第一步:根據(jù)pointCut切點(diǎn)規(guī)則匹配的joinPoint;
第二步:將Advise插入到目標(biāo)JoinPoint中。
這樣在程序運(yùn)行時(shí)被重構(gòu)的連接點(diǎn)將會回調(diào)Advise方法,就實(shí)現(xiàn)了AspectJ代碼與目標(biāo)代碼之間的連接。

JoinPoint類包UML:

2.3.1 Before、After(AfterThrowing)插入示意圖

Before和After的插入其實(shí)就是在匹配到的JoinPoint調(diào)用的前后插入我們編寫的Before\After的Advise方法,以此來達(dá)到在目標(biāo)JoinPoint執(zhí)行之前先進(jìn)入Advise方法,執(zhí)行之后進(jìn)入Advise方法。
如下圖所示,目標(biāo)JoinPoint為FuncB()方法,需要在他執(zhí)行前后進(jìn)行AOP截獲:

2.3.2 Around替換邏輯示意圖

總體來說,使用了代理+閉包的方式進(jìn)行替換,將原方法體放置到新的函數(shù)中替換,通過一個(gè)單獨(dú)的閉包拆分來執(zhí)行,相當(dāng)于對目標(biāo)JoinPoint進(jìn)行了一個(gè)代理。

2.3.3 代碼分析

下面的Example作為目標(biāo)源碼,我們對它的printLog()方法進(jìn)行替換、對getValue()方法調(diào)用前后插入Advise方法。

public class Example {String value = "value"?public void printLog() {String str = getValue()?}public String getValue() {return value?} }

切面代碼:

@Aspect public class LogAspect{ //所有實(shí)例方法調(diào)用截獲 private static final String INSTANCE_METHOD_CALL = "call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)" @Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() { }//實(shí)例方法調(diào)用前后Advice @Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) { printLog(joinPoint, "before instance call")? }@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) { printLog(joinPoint, "after instance call")? }//所有實(shí)例方法執(zhí)行截獲 private static final String INSTANCE_METHOD_EXECUTING = "execution(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)" @Pointcut(INSTANCE_METHOD_EXECUTING) public void instanceMethodExecuting() { }//實(shí)例方法執(zhí)行Advice @Around("instanceMethodExecuting()") public Object InstanceMethodExecutingAround(ProceedingJoinPoint joinPoint){Log.e(getClass().getSimpleName(),"InstanceMethodExecuting()")?Object result = printLog(joinPoint, "instance executing")?return result?} }

反編譯后的結(jié)果
網(wǎng)上給的反編譯過程都是apktool——>dex2jar——>jd-gui,這個(gè)我用hotel_road_map的debug包試過,反編譯出來的jar包里面只有幾個(gè)系統(tǒng)類,不知道什么原因,其他的包又可以正常反編譯。
推薦一個(gè)反編譯工具:jadx(可以直接反編譯apk)。

public class Example { private static final StaticPart ajc$tjp_0 = null? private static final StaticPart ajc$tjp_1 = null? private static final StaticPart ajc$tjp_2 = null? String TAG = "Example"? String value = "value"? static { ajc$preClinit()? } //初始化連接點(diǎn)靜態(tài)部分:方法名、參數(shù)列表、返回值、包路徑等等。 private static void ajc$preClinit() { Factory factory = new Factory("Example.java", Example.class)? ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_CALL, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadmap.Ex"); ajc$tjp_1 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "printLog", "com.meituan.hotel.roadm"); ajc$tjp_2 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadm"); }//原方法的閉包,在Aspect切面中joinPoint.proceed()會調(diào)用該閉包 public class AjcClosure1 extends AroundClosure {public AjcClosure1(Object[] objArr) {super(objArr)? }public Object run(Object[] objArr) {Object[] objArr2 = this.state?Example.printLog_aroundBody0((Example) objArr2[0], (JoinPoint) objArr2[1])?return null?} } //原方法真正的方法體,在閉包中被調(diào)用static final void printLog_aroundBody0(Example ajc$this, JoinPoint joinPoint) {JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, ajc$this, ajc$this)?try {//@Before的Advise被插入了目標(biāo)代碼調(diào)用之前LogAspect.aspectOf().beforInstanceCall(makeJP)?String str = ajc$this.getValue()?} finally {//@After的Advise被插入到目標(biāo)代碼調(diào)用之后,通過Finally強(qiáng)制執(zhí)行After邏輯,每一個(gè)BeforeLogAspect.aspectOf().afterInstanceCall(makeJP)?}}//原來的printLog()方法被AspectJ給替換了,替換成為鏈接AspectJ和源代碼的橋梁,真正的方法體被放在了新的方法中。public void printLog() {//連接點(diǎn)構(gòu)造JoinPoint makeJP = Factory.makeJP(ajc$tjp_1, this, this)?//將連接點(diǎn)與原方法的閉包連接,這樣就可以在AspectJ的JoinPoint中通過joinPoint.proceed()調(diào)用閉包執(zhí)行原方法。LogAspect.aspectOf().InstanceMethodExecutingAround(new AjcClosure1(new Object[]{this, makeJP}).linkClosureAndJoinPoint( }public String getValue() { JoinPoint makeJP = Factory.makeJP(ajc$tjp_2, this, this)?return (String)LogAspect.InstanceMethodExecutingAround(new AjcClosure3(new Object[]{this, makeJP}).linkClosureAndJoinPoint();} }

Before、After(AfterThrowing)插入分析

Before\After的插入調(diào)用比較簡單,通過PointCut定位匹配到JoinPoint之后,將我們編寫的Before\After的切面方法直接插入到目標(biāo)JoinPoint前后即可。這樣就可以改變原有的代碼調(diào)用軌
跡,在目標(biāo)方法調(diào)用前后增加我們自己的AOP方法。

//原方法真正的方法體,在閉包中被調(diào)用 static final void printLog_aroundBody0(Example ajc$this, JoinPoint joinPoint) { JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, ajc$this, ajc$this)? try { //@Before的Advise被插入了目標(biāo)代碼調(diào)用之前 LogAspect.aspectOf().beforInstanceCall(makeJP)? String str = ajc$this.getValue()? } finally { //@After的Advise被插入到目標(biāo)代碼調(diào)用之后,通過Finally強(qiáng)制執(zhí)行After邏輯,每一個(gè)Before LogAspect.aspectOf().afterInstanceCall(makeJP)? } }

Around替換代碼分析
JoinPoint為printLog()方法,是被Around替換的,反編譯后的部分代碼如下:
首先,在靜態(tài)初始化的時(shí)候,通過Factory會為每一個(gè)JPoint先構(gòu)造出靜態(tài)部分信息StaticPart。

//初始化連接點(diǎn)靜態(tài)部分:方法名、參數(shù)列表、返回值、包路徑等等。 private static void ajc$preClinit() {Factory factory = new Factory("Example.java", Example.class)?ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_CALL, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadmap.Exa");ajc$tjp_1 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "printLog", "com.meituan.hotel.roadma");ajc$tjp_2 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadma"); } public JoinPoint.StaticPart makeSJP(String kind, String modifiers, String methodName, String declaringType, String paramTypes,St //構(gòu)造方法簽名實(shí)例,其中存儲著方法的靜態(tài)信息 Signature sig = this.makeMethodSig(modifiers, methodName, declaringType, paramTypes, paramNames, "", returnType)? return new JoinPointImpl.StaticPartImpl(count++, kind, sig, makeSourceLoc(l, -1))? }

其次,將printLog方法體替換,以XXX_aroundBodyN(args)命名,原方法體被替換如下:

//原來的printLog()方法被AspectJ給替換了,替換成為鏈接AspectJ和源代碼的橋梁,真正的方法體被放在了新的方法中。 public void printLog() { //連接點(diǎn)構(gòu)造 JoinPoint makeJP = Factory.makeJP(ajc$tjp_1, this, this)? //將連接點(diǎn)與原方法的閉包連接,這樣就可以在AspectJ的JoinPoint中通過joinPoint.proceed()調(diào)用閉包執(zhí)行原方法。 LogAspect.aspectOf().InstanceMethodExecutingAround(new AjcClosure1(new Object[]{this, makeJP}).linkClosureAndJoinPoint( }

AroundClosure閉包,將運(yùn)行時(shí)對象和當(dāng)前連接點(diǎn)JP對象傳入其中,調(diào)用linkClosureAndJoinPoint()進(jìn)行兩端的綁定,這樣在Around中就可以通過ProceedingJoinPoint.proceed()調(diào)用AroundClosure,進(jìn)而調(diào)用目標(biāo)方法。

public abstract class AroundClosure {protected Object[] state?protected Object[] preInitializationState?public AroundClosure() {}public AroundClosure(Object[] state) {this.state = state?}public ProceedingJoinPoint linkClosureAndJoinPoint() {//獲取執(zhí)行鏈接點(diǎn),默認(rèn)數(shù)組最后一個(gè)是連接點(diǎn)ProceedingJoinPoint jp = (ProceedingJoinPoint)state[state.length-1]?//設(shè)置執(zhí)行時(shí)閉包jp.set$AroundClosure(this)?return jp?} }

JoinPointImpl,包括一個(gè)JoinPoint的靜態(tài)部分和實(shí)例部分:

class JoinPointImpl implements ProceedingJoinPoint {//JP靜態(tài)部分static class StaticPartImpl implements JoinPoint.StaticPart {String kind?Signature signature?SourceLocation sourceLocation?private int id?//省略}Object _this?Object target?Object[] args?org.aspectj.lang.JoinPoint.StaticPart staticPart?//省略....// To proceed we need a closure to proceed onprivate AroundClosure arc?public void set$AroundClosure(AroundClosure arc) {this.arc = arc?}//通過proceed()調(diào)用閉包arc的run方法,并且傳入JP的執(zhí)行狀態(tài):參數(shù)列表等。進(jìn)而調(diào)用原方法體執(zhí)行public Object proceed() throws Throwable {//when called from a before advice, but be a no-opif (arc == null)return null?elsereturn arc.run(arc.getState())?}//省略.... }

2.4 AspectJ切面編寫

2.4.1 日志打印

(1)追蹤某些特定方法的調(diào)用日志,統(tǒng)計(jì)調(diào)用的頻率
(2)關(guān)注某類方法的日志
(3)全局日志的打印
AOP示例代碼:

/** * Created by malingyi on 2017/3/22. */ /** * 日志打印。(分靜態(tài)調(diào)用、靜態(tài)執(zhí)行、實(shí)例調(diào)用、實(shí)例執(zhí)行四類日志) */ @Aspect public class LogAspect { //所有靜態(tài)方法調(diào)用截獲 private static final String STATIC_METHOD_CALL = "call(static * com.meituan.hotel.roadmap..*.*(..))"? @Pointcut(STATIC_METHOD_CALL) public void staticMethodCutting() { } @Before("staticMethodCutting()") public void beforStaticCall(JoinPoint joinPoint) { printLog(joinPoint, "before static call")? } @After("staticMethodCutting()") public void afterStaticCall(JoinPoint joinPoint) { printLog(joinPoint, "after static call")? } //所有實(shí)例方法調(diào)用截獲 private static final String INSTANCE_METHOD_CALL = "call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)"? @Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() { } //實(shí)例方法調(diào)用前后Advice @Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) { printLog(joinPoint, "before instance call")? } @After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) { printLog(joinPoint, "after instance call")? } //所有靜態(tài)方法執(zhí)行截獲 private static final String STATIC_METHOD_EXECUTING = "execution(static * com.meituan.hotel.roadmap..*.*(..)) && !within(com.example.monitor.*)"? @Pointcut(STATIC_METHOD_EXECUTING) public void staticExecutionCutting() { } //所有實(shí)例方法執(zhí)行截獲 private static final String INSTANCE_METHOD_EXECUTING = "execution(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)&& !within(com.example.monitor.*)"? @Pointcut(INSTANCE_METHOD_EXECUTING) public void instanceMethodExecuting() { } //靜態(tài)方法執(zhí)行Advice @Around("staticExecutionCutting()") public Object staticMethodExecuting( ProceedingJoinPoint joinPoint) { Log.e(getClass().getSimpleName(), "staticMethodExecuting()")? Object result = printLog(joinPoint, "static executing")? return result? } //實(shí)例方法執(zhí)行Advice @Around("instanceMethodExecuting()") public Object InstanceMethodExecuting( ProceedingJoinPoint joinPoint) { Log.e(getClass().getSimpleName(), "InstanceMethodExecuting()")? Object result = printLog(joinPoint, "instance executing")? return result? } /** * 日志打印和統(tǒng)計(jì) * @param joinPoint * @param describe * @return */ private Object printLog(JoinPoint joinPoint, String describe) { Signature signature = joinPoint.getSignature()? MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature()? try { if (joinPoint instanceof ProceedingJoinPoint) { return ((ProceedingJoinPoint) joinPoint).proceed(joinPoint.getArgs())? } } catch (Throwable throwable) { throwable.printStackTrace()? } finally { Log.e(getClass().getSimpleName(), describe + " : " + signature.toLongString())? } return null? } }

結(jié)果:

04-24 02:39:51.388 3081-3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04-24 02:39:51.388 3081-3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public int
com.meituan.hotel.roadmap.MainActivity.ViewPagerFragmentAdapter.getCount()
04-24 02:39:51.418 3081-3171/com.meituan.hotel.roadmap E/Surface: getSlotFromBufferLocked: unknown buffer: 0xf2ca76e0
04-24 02:39:51.667 3081-3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04-24 02:39:51.667 3081-3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04-24 02:39:51.667 3081-3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public void
com.meituan.hotel.roadmap.RoadMapApplication.2.onActivityStopped(android.app.Activity)
04-24 02:39:51.667 3081-3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : protected void com.meituan.hotel.roadmap.BaseActivity.onStop()
04-24 02:39:51.667 3081-3081/com.meituan.hotel.roadmap E/ContentValues: HotelDetailActivity 停留時(shí)間: 4657.063 ms
04-24 02:39:51.668 3081-3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04-24 02:39:51.668 3081-3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public void
com.meituan.hotel.roadmap.RoadMapApplication.2.onActivityDestroyed(android.app.Activity)

2.4.2 耗時(shí)監(jiān)控

示例代碼1:
步驟:
1、編寫AspectJ的語法,橫切需要關(guān)注的切點(diǎn)
2、在其執(zhí)行前后增加計(jì)時(shí)?
3、輸出時(shí)間日志,進(jìn)行統(tǒng)計(jì)分析

/** * 時(shí)間監(jiān)控 */ @Aspect public class TimeMonitorAspect { //橫切項(xiàng)目中所有Activity的子類,以Layout命名、以及它的子類的所有方法的執(zhí)行 private static final String POINTCUT_METHOD = "(execution(* android.app.Activity+.*(..)) ||execution(* *..Layout+.*(..)))&& within(com.meituan.hotel.roadmap.*)" @Pointcut(POINTCUT_METHOD) public void methodAnnotated() { } /** * 截獲原方法的執(zhí)行,添加計(jì)時(shí)?,監(jiān)控單個(gè)方法的耗時(shí) * @throws Throwable */ @Around("methodAnnotated()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { //初始化計(jì)時(shí)? final StopWatch stopWatch = new StopWatch()? //開始監(jiān)聽 stopWatch.start()? //調(diào)用原方法的執(zhí)行。 Object result = joinPoint.proceed()? //監(jiān)聽結(jié)束 stopWatch.stop()? //日志打印 printLog(joinPoint, stopWatch)? return result? } private void printLog(JoinPoint joinPoint, StopWatch stopWatch) { //獲取方法信息對象 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature()? String className? //獲取當(dāng)前對象,通過反射獲取類別詳細(xì)信息 className = joinPoint.getThis().getClass().getName()? String methodName = methodSignature.getName()? String msg = buildLogMessage(methodName, stopWatch.getTotalTime(1))? //日志存儲、打印 TimeMonitorLog.log(new MethodMsg(className, msg, (long) stopWatch.getTotalTime(1)))? // TimeMonitorLog.writeToSDCard(new Path())? //日志存儲 // TimeMonitorLog.ReadIn(new Path())? //日志讀取 }/** * 創(chuàng)建一個(gè)日志信息 * @param methodName 方法名 * @param methodDuration 執(zhí)行時(shí)間 */ private static String buildLogMessage(String methodName, double methodDuration) { StringBuilder message = new StringBuilder()? message.append(methodName)? message.append(" --> ")? message.append("[")? message.append(methodDuration)? if (StopWatch.Accuracy == 1) { message.append("ms")? } else { message.append("mic")? } message.append("] \n")? return message.toString()? } }

結(jié)果:

03-27 04:31:35.681 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onStop --> [0.286ms]
03-27 04:31:39.040 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.HotelDetailActivity getLayoutId --> [0.003ms]
03-27 04:31:39.047 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onCreate --> [7.217ms]
03-27 04:31:39.048 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.HotelDetailActivity onCreate --> [7.972ms]
03-27 04:31:39.050 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onStart --> [0.276ms]

示例代碼2——監(jiān)控Activity頁面的停留時(shí)間
步驟:
1、編寫橫切項(xiàng)目中Activity的onStart()、onStop()的切點(diǎn)語法
2、然后在onStart()切點(diǎn)執(zhí)行之前啟動計(jì)時(shí)?,將其與該頁面對象存入Map中進(jìn)行綁定
3、在onStop()切點(diǎn)執(zhí)行完畢之后,從通過該對象從Map中獲取計(jì)時(shí)?,然后結(jié)束計(jì)時(shí),輸出日志。

/** * 時(shí)間監(jiān)控 */ @Aspect public class TimeMonitorAspect { private static final String TAG = "TimeMonitorAspect"? //存放<頁面對象,計(jì)時(shí)?> private HashMap<Object,StopWatch> map = new HashMap<>()? /** * 橫切頁面的onStart和onStop方法,監(jiān)控兩個(gè)方法之間的耗時(shí) */ @Pointcut("execution(* *..Activity+.onStart(..))&&this(java.lang.Object)&& within(com.meituan.hotel.roadmap.*)") public void onStart(){} @Pointcut("execution(* *..Activity+.onStop(..))&&this(java.lang.Object)&& within(com.meituan.hotel.roadmap.*)") public void onStop(){} @Pointcut("onStart() && !cflowbelow(onStart())") public void realOnStart(){} @Pointcut("onStop() && !cflowbelow(onStop())") public void realOnStop(){} /** * 在onCreate()調(diào)用時(shí),開啟該頁面的計(jì)時(shí)?,將計(jì)時(shí)?存入HashMap<Object,StopWatch>中。 * @param joinPoint * @return */ @Around("realOnStart()") public Object AroundOnStart(ProceedingJoinPoint joinPoint){ Object result = null? Object target = joinPoint.getTarget()? StopWatch stopWatch = new StopWatch()? if (target != null){ map.put(target,stopWatch)? } try { stopWatch.start()? result = joinPoint.proceed(joinPoint.getArgs())? } catch (Throwable throwable) { throwable.printStackTrace()? } return result? } /** * 在onStop()結(jié)束時(shí),從HashMap<Object,StopWatch>中獲取該計(jì)時(shí)?,停止該頁面的計(jì)時(shí)?,并將時(shí)間打印出來。 * @param joinPoint * @return */ @Around("realOnStop()") public Object AroundOnStop(ProceedingJoinPoint joinPoint){ Object result = null? Object target = joinPoint.getTarget()? StopWatch stopWatch = null? if (target != null){ stopWatch = map.get(target)? } try { result = joinPoint.proceed(joinPoint.getArgs())? if (stopWatch != null){ stopWatch.stop()? //打印日志 Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " 停留時(shí)間: "+ stopWatch.getTotalTimeMillis() + " ms" } } catch (Throwable throwable) { throwable.printStackTrace()? } return result? } }

結(jié)果:

03-27 05:01:16.629 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: HotelDetailActivity 停留時(shí)間: 4170.12 ms
03-27 04:31:39.465 27210-27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: MainActivity 停留時(shí)間: 4112.293 ms


2.4.3 異常處理

示例代碼1——截獲謀類異常

/** * 異常處理 */ @Aspect public class ExceptionHandleAspect { private static final String TAG = "ExceptionHandleAspect"? /** * 截獲空指針異常 * @param e */ @Pointcut("handler(java.lang.NullPointerException)&&args(e)") public void handle(NullPointerException e){ } /** * 在catch代碼執(zhí)行之前做一些處理 * @param joinPoint * @param e 異常參數(shù) */ @Before(value = "handle(e)",argNames = "e") public void handleBefore(JoinPoint joinPoint,NullPointerException e){ Log.e(TAG,joinPoint.getSignature().toLongString()+" handleBefore() :"+e.toString())? //匯總處理 } }

結(jié)果:

03-27 06:46:48.700 11618-11618/com.meituan.hotel.roadmap E/ExceptionHandleAspect: MainActivity afterThrowing() :java.lang.NullPointerException: null

項(xiàng)目中所有的NullPointerException的catch()方法執(zhí)行之前都會被截獲。

示例代碼2——截獲指定方法的異常

/** * 異常處理 */ @Aspect public class ExceptionHandleAspect { private static final String TAG = "ExceptionHandleAspect"? /** * 截獲某一個(gè)方法拋出的異常 */ @Pointcut("call(* com.meituan.hotel.roadmap.*.initTabLayout(..))") public void afterThrow(){} /** * 在異常拋出后,該操作優(yōu)先于下一個(gè)切點(diǎn)的@Before() * @param joinPoint * @param e 異常參數(shù) */ @AfterThrowing(pointcut = "afterThrow()",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Exception e){ Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " afterThrowing() :" + e.toString())? } }

結(jié)果:

03-27 06:46:48.700 11618-11618/com.meituan.hotel.roadmap E/ExceptionHandleAspect: MainActivity afterThrowing() :java.lang.NullPointerException: null

在MainActivity上調(diào)用initTabLayout()方法時(shí)拋出一個(gè)空指針,拋出后被截獲了。

2.4.4 降級替代方案——吐司

在Android 5.0以后,有些手機(jī)關(guān)閉通知權(quán)限會導(dǎo)致Toast通知無法顯示。有些項(xiàng)目在這之前就已經(jīng)有很多Toast的使用了,那么這個(gè)時(shí)候就需要在Toast代碼前后做權(quán)限驗(yàn)證,然后再使用備用方法替代。

使用AOP就可以將這種重復(fù)的代碼聚焦在一處進(jìn)行處理。類似于這樣的,在代碼工程迭代過程中,會大量重復(fù)用到的降級替代都可以使用AOP的思想。
步驟:
1、自定義一個(gè)Toast的View,該View不依賴于Notification通知權(quán)限。
2、截獲項(xiàng)目中調(diào)用Toast的.show()方法。

@Aspect public class ToastAspect { private static final String TAG = "toastAspect"? @Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.meituan..*)|| within(com.sankuai..*))") public void toastShow() { } @Pointcut("toastShow() && !cflowbelow(toastShow())") public void realToastShow() { } @Around("realToastShow()") public void toastShow(ProceedingJoinPoint point) { try { Toast toast = (Toast) point.getTarget()? Context context = (Context) getValue(toast, "mContext")? //如果當(dāng)前沒有context意味著可能頁面被回收,或者的版本在19以上且通知可用,執(zhí)行系統(tǒng)的Toast方案交給系統(tǒng)處理 if (context == null || Build.VERSION.SDK_INT >= 19 && NotificationManagerCompat.from(context).areNotificationsEnabl //use system function point.proceed(point.getArgs())? } else {//如果context存在,并且通知不可用,則使用自定義的Toast //Toast params int mDuration = toast.getDuration()? View mNextView = toast.getView()? int mGravity = toast.getGravity()? int mX = toast.getXOffset()? int mY = toast.getYOffset()? float mHorizontalMargin = toast.getHorizontalMargin()? float mVerticalMargin = toast.getVerticalMargin()? new MToast(context instanceof Application ? context : context.getApplicationContext()) .setDuration(mDuration) .setView(mNextView) .setGravity(mGravity, mX, mY) .setMargin(mHorizontalMargin, mVerticalMargin).show()? } } catch (Throwable exception) { //ignore } } // TODO: 2016/12/14 toast.cancel() can't be work with MToast /** * 通過字段名從對象或?qū)ο蟮母割愔械玫阶侄蔚闹?* * @param object 對象實(shí)例 * @param fieldName 字段名 * @return 字段對應(yīng)的值 * @throws Exception */ public static Object getValue(Object object, String fieldName) throws Exception { if (object == null || TextUtils.isEmpty(fieldName)) { return null? } Field field = null? Class<?> clazz = object.getClass()? for (? clazz != Object.class? clazz = clazz.getSuperclass()) { try { field = clazz.getDeclaredField(fieldName)? field.setAccessible(true)? return field.get(object)? } catch (Exception e) { //ignore } } return null? } }

2.4.5 其他的系統(tǒng)橫切關(guān)注點(diǎn)問題

使用特點(diǎn):

  • 關(guān)注點(diǎn)具有普遍性需求,代碼散亂分布在工程各處,可以抽出共同的代碼。
  • 訪問控制,例如:字段、方法的訪問前做一些驗(yàn)證,訪問之后做一些處理。
  • 代碼約束,例如:限制某些方法只能在特定的地方使用,否則在編譯期間拋出Error錯(cuò)誤或者Warning。
  • 項(xiàng)目中需要臨時(shí)插入一些方法、邏輯,但是不希望影響到原工程,易插易拔。

三、相關(guān)問題

3.1 編織速度

(1)盡量使用精確的匹配規(guī)則,降低匹配時(shí)間。
(2)排除不需要掃描的包。

// AspectJ aspectj { disableWhenDebug true javartNeeded true // 支付方排除 exclude group: 'com.sankuai.pay', module: 'buymodel' exclude group: 'com.meituan.android.cashier', module: 'library' // 第三方異常包排除 exclude group: 'com.dianping.nova.common', module: 'push' exclude group: 'org.freemarker', module: 'freemarker' compileOptions { defaultJavaVersion = JavaVersion.VERSION_1_7 } }

(3)硬件配置升級。

3.2 調(diào)試工具

Eclipse和Intellij 12支持AJDT調(diào)試工具,但是目前AndroidStudio并不支持,只能在Gradle構(gòu)建時(shí)查看日志。
(1)切點(diǎn):

(2)目標(biāo)代碼:

Gradle構(gòu)建日志:

總結(jié)

以上是生活随笔為你收集整理的AOP之@AspectJ技术原理详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 亚洲视频免费播放 | 国产二区免费 | 久久精品视频在线免费观看 | 四虎精品欧美一区二区免费 | 嫩草视频国产 | 欧美不卡网 | 图书馆的女友在线观看 | 成年人在线观看网站 | 熟女丝袜一区 | 美女视频一区二区 | 欧美大片91 | av一级久久 | 二级黄色片 | 国产成人小视频在线观看 | 欧美精品一级在线观看 | 亚洲成人高清在线观看 | 牛牛澡牛牛爽一区二区 | 国产亚洲精品成人 | 丰满人妻av一区二区三区 | 国产高清在线精品 | 婷婷丁香久久 | 黄色精品一区二区 | 亚洲免费片 | 精品成人中文无码专区 | 伊人久久亚洲综合 | 国产成人免费在线 | 亚洲咪咪 | 久久一级免费视频 | 亚洲欧洲日韩在线 | 成人精品电影 | www.精品国产 | 精品人妻一区二区三区四区不卡 | 91精品国产色综合久久不卡粉嫩 | 日本极品丰满ⅹxxxhd | 波多野结衣伦理 | 高h文在线 | 成人免费毛片日本片视频 | 99久久精品一区 | jizz欧美大全| 午夜婷婷在线观看 | 在线观看av片 | 欧美一区二区人人喊爽 | 亚洲综合自拍偷拍 | 亚洲美女免费视频 | 久艹av| 深夜视频在线观看免费 | 欧美一级高清片 | 哺乳喂奶一二三区乳 | 欧美xxxx喷水| 四虎成人精品永久免费av | 色天堂在线视频 | 午夜看片在线观看 | 四虎精品一区二区三区 | 拔插拔插影库 | 午夜尤物 | 中文字幕欧美日韩 | 日本成人福利视频 | 一本一道久久综合狠狠老精东影业 | 久久伊人草| 污污视频在线观看网站 | 国产视频一区二区三区在线观看 | 中国免费黄色片 | 老牛嫩草二区三区观影体验 | 欧美一区不卡 | xxx性视频| 国产精品毛片一区二区三区 | 麻豆免费观看视频 | 日本黄色三级 | 美国一区二区三区 | 欧洲色网| 国产男同gay网站 | 极品久久久 | 午夜精品福利在线观看 | 不卡av免费在线观看 | 日韩av网页 | 色牛av | 一区二区精彩视频 | 黑人巨大精品 | 香蕉影院在线 | 国产伦精品一区二区三区免费视频 | 户外露出一区二区三区 | 一卡二卡三卡在线视频 | 成人免费毛片嘿嘿连载 | 国产精品第一 | 桃谷绘里香在线播放 | 亚欧成人在线 | 色爱成人综合 | www国产精品内射老熟女 | 色屁屁www影院免费观看入口 | tube极品少妇videos | 美女被爆操网站 | 可以免费观看av | 性天堂网 | 日韩视频一区 | 狠狠搞av| 成熟妇人a片免费看网站 | 日本在线观看一区二区 | 91久久精品国产91久久 | 亚洲天堂男 |