AOP之@AspectJ技术原理详解
- 一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ù)列表) <異常列表>?)
- 精確匹配
- 單一模糊匹配
- 組合模糊匹配
(3)獲取參數(shù)
- 通過聲明參數(shù)語法arg()顯示獲取參數(shù)
- 通過joinPoint.getArg()獲取參數(shù)列表
(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
(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)。
Before、After(AfterThrowing)插入分析
Before\After的插入調(diào)用比較簡單,通過PointCut定位匹配到JoinPoint之后,將我們編寫的Before\After的切面方法直接插入到目標(biāo)JoinPoint前后即可。這樣就可以改變原有的代碼調(diào)用軌
跡,在目標(biāo)方法調(diào)用前后增加我們自己的AOP方法。
Around替換代碼分析
JoinPoint為printLog()方法,是被Around替換的,反編譯后的部分代碼如下:
首先,在靜態(tài)初始化的時(shí)候,通過Factory會為每一個(gè)JPoint先構(gòu)造出靜態(tài)部分信息StaticPart。
其次,將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示例代碼:
結(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ì)分析
結(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í),輸出日志。
結(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()方法。
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)排除不需要掃描的包。
(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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无人参与应答文件包含的产品密钥无效,删除
- 下一篇: 项目交付二三事