--- title: 'AOP AspectJ' disqus: kyleAlien --- AOP AspectJ === ## OverView of Content AOP 是透過在字節碼插樁來達到 Hook 原來的程式,當然它可以配合 Java 的反射、動態代理一起使用 [TOC] ## AspectJ - gradle 依賴 * 要使用 AspectJ 首先需要先設定必須的依賴 1. project (根) build.gradle 添加依賴 ```groovy= buildscript { repositories { google() mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:7.0.4" // 添加 aspectj classpath 'org.aspectj:aspectjtools:1.9.6' } } ``` 2. 在相對應需要使用到 AspectJ 的 Module 中的 build.gradle 添加依賴 ```groovy= plugins { id 'com.android.application' // application or library } android { ... 省略 } dependencies { ... 省略其他 implementation 'org.aspectj:aspectjrt:1.9.6' } ``` 3. 在 Application or library 中的 gradle 添加必要資訊,Application & library 的差異在 Variants 取得的方式不同 ```groovy= plugins { id 'com.android.application' } android { ... 省略 } dependencies { ... 省略其他 implementation 'org.aspectj:aspectjrt:1.9.6' } import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main final def log = project.logger // Application & library 的差異,以下兩個是依照你的 Module 做選擇 // 1. for application final def variants = project.android.applicationVariants // 2. for library // final def variants = android.libraryVariants variants.all { variant -> // 如果不開啟的話則 AspectJ 無法織入 你要插入的程式 // if (!variant.buildType.isDebuggable()) { // log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") // return // } JavaCompile javaCompile = variant.javaCompileProvider.get() javaCompile.doLast { String[] args = ["-showWeaveInfo", "-1.8", // java1.8 "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] log.debug "ajc args: " + Arrays.toString(args) def buildType = variant.buildType.name String[] kotlinArgs = ["-showWeaveInfo", "-1.8", "-inpath", project.buildDir.path + "/tmp/kotlin-classes/" + buildType, "-aspectpath", javaCompile.classpath.asPath, "-d", project.buildDir.path + "/tmp/kotlin-classes/" + buildType, "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] MessageHandler handler = new MessageHandler(true) new Main().run(args, handler) new Main().run(kotlinArgs, handler) for (IMessage message : handler.getMessages(null, true)) { switch (message.getKind()) { case IMessage.ABORT: case IMessage.ERROR: case IMessage.FAIL: log.error message.message, message.thrown break case IMessage.WARNING: log.warn message.message, message.thrown break case IMessage.INFO: log.info message.message, message.thrown break case IMessage.DEBUG: log.debug message.message, message.thrown break } } } } ``` ## AspectJ - 使用語法 由於 AspectJ 會分析它自己規範格式的語法,並透過你寫的語法在編譯時找到相對應的 constructor、method、field、static、異常 等等 ### AspectJ 使用規則 1. **切面**:@AspectJ 它聲明一個類,是要執行的切面,註解在 Class 上 (如果沒有 @Aspect 註解,則無法做 `切點` 的切入) 2. **切點**:@Pointcut 描述要對程式切入點,會依照該註解內的字串(依照規則,這個規則晚點會說明) 找尋相對應的 Construct、Method、Field... 等等做插入 ```java= // 切面,如果沒有 Aspect 就無法實現以下程式的插入 @Aspect public class HookLambdaOnClickInterface { // 切點 // 這邊的範例是切入所有 名為 testPointCut 函數的點 @Pointcut("execution(* *..*.testPointCut())") public void myCutPoint() { // 這只是個切入點,那要如何操作這個切入點呢 ? // 這就要使用到 Advice 切點 } } ``` 3. **Advice 切點**:Advice 系列註解是在切點的前、中、後做切入 ```java= // 切面,如果沒有 Aspect 就無法實現以下程式的插入 @Aspect public class HookLambdaOnClickInterface { // 切點 // 這邊的範例是切入所有 android.view.View.OnClickListener.onClick 的實作類 // Around 之後會再解釋 @Around("execution(* android.view.View.OnClickListener.onClick(..))") public void executionClick(ProceedingJoinPoint joinPoint) { try { // 可在前面做些事情... joinPoint.proceed(joinPoint.getArgs()); // 執行原來的 Function,也可以對傳入的參數進行操作 // 也可以在後免做些事情... } catch (Throwable throwable) { throwable.printStackTrace(); } } } ``` ### PointCut Pattern 規則分析 * 我們在使用 PointCut 註解時,需要按照 Aspect 規定的規則進行撰寫,這樣它在編譯時才會知道要在哪裡插入我們需要的方法 ```java= @Aspect public class PointCutPattern { @Pointcut(/* 這邊要寫啥呢 ?*/) public void myPattern() { } } ``` > ![](https://i.imgur.com/VMftX14.png) * 使用 PointCut 規範的字串:對照你要切入的點,在註解內寫字串,而字串的規則如下 | 抓取點 | Pattern | 規則 (**參數之間要有空格**) | | -------- | -------- | -------- | | Method | MethodPattern | [@註解] [訪問權限] <返回類型> [類名.]<方法名(參數)> [throws 類型] | | Constructor | ConstructorPattern | [@註解] [訪問權限] [類名.]<new(參數)> [throws 類型] | | Field | FieldPattern | [@註解] [訪問權限] <變量類型> [類名.]<變量名> [throws 類型] | | Type | TypePattern | 指定類,與其他 Pattern 涉及到的類型規則也是一樣,可以使用 !、''、..、+ | * ! 表示取反 * '' 匹配除 . 外的所有字符串 * \* 單獨使用事表示匹配任意類型 * .\. 匹配任意字符串 * .\. 單獨使用時表示匹配任意長度任意類型 * \+ 匹配其自身及子類 * ... 表示不定個數 * PointCut 範例 ```java= // 範例 1 // 1. 先看 execution: Function 執行中插入 // 2. 在看() 內: // 2-1: 返回值 *,也就是 '匹配任意長度類型' // 2-2: 任何包中的任何類 *..* // 2-3: 方法名 testPointCut // 2-4: 無參數 // @Pointcut("execution(* *..*.testPointCut())") // ---------------------------------------------------------- // 範例 2 // 1. 先看 execution: Function 執行中插入 // 2. 在看() 內: // 2-1: 註解為 @com.alien.plugin_aspect_j.aspect_annotation.MyHook // 2-2: 任何類中 * // 2-3: 方法名為任意 * // 2-4: .. 匹配任意字符串 @Pointcut("execution(@com.alien.plugin_aspect_j.aspect_annotation.MyHook * *(..))") public void cutAnnotation(MyHook myHook) { } ``` ### PointCut - Join Point 切點 * 以下先大概介紹,詳細請見 [**PointCuts**](https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html) 官網 * PointCut 是註解一個 **切入點**,它會在編譯時搜尋符合你寫的 Method、construct... 等等 首先先找你要切入的點,以下有分為有分為 Method、Constructor、Field、Type 這幾個種類,而它相對的 Pattern 下面會介紹 | 切入點(Join Point) | PointCut 關鍵字 | 說明 | | -------- | -------- | -------- | | Method | execution(MethodPattern) | 方法執行時 | | Method | call(MethodPattern) | 方法 **被** 調用時觸發 | | Constructor | execution(ConstructorPattern) | 建構執行時 | | Constructor | call(ConstructorPattern) | 建構 **被** 調用時 | | Field | get(FieldPattern) | 讀取屬性 | | Field | set(FieldPattern) | 設定屬性 | | Type | staticinitialization(TypePattern) | static 初始化 | | Type | handler(TypePattern) | 異常處理 | 以下以常使用的 `execution`、`call` 說明 ```java= // 偽代碼 // // 切入面 @AspectJ public class myCutClz { // 切入點 @Around(* *..*.myClz.originalFunction()) public void myCut() { Log.e("TEST", "myCut"); } } public class myClz { public static void main(String[] args) { new myClz().originalFunction(); } void originalFunction() { System.out.println("Hello"); } } // ------------------------------------------- // 偽代碼 // // java -> class 後的狀態 // 使用 execution 的概念,進到 originalFunction 函數後,才開始進行插入 void originalFunction() { beforeExe(); System.out.println("Hello"); afterExe(); } // ------------------------------------------- // 偽代碼 // // 使用 call 的概念 public static void main(String[] args) { new myClz().hookByExecution(); } void hookByExecution() { // 偽代碼 beforeCall(); originalFunction(); afterCall(); } void originalFunction() { System.out.println("Hello"); } ``` > ![](https://i.imgur.com/NMWlQqJ.png) ### PointCut - 進階切點 * PointCut 除了上面說的 Join Point 以外,還可以有其他方式可以使用 | 切入點(Join Point) | 關鍵字 | 說明 | | -------- | -------- | -------- | | Type | within(TypePattern) | 尋找符合 TypePattern 的 Join Point | | Method | withincode(MethodPattern) | 尋找符合 MethodPattern 的 Join Point | | Constructor | withincode(ConstructorPattern) | 尋找符合 ConstructorPattern 的 Join Point | | Pointcut | cflow(Pointcut) | 透過 Point Cut 選擇出的切入點,並針對該 Point cut 中所有的函數進行 Join Point,**包括原 Point Cut 的點** | | Pointcut | cflowbelow(Pointcut) | 同上,不過它 **不包括 Point Cut 的點** | | Type or Id | this(Type or Id) | Join Point 的 this 對象是否 instanceof 指定的 Type or id 的類型 | | Type or Id | target(Type or Id) | Join Point 所在的對象 (eg. call or execution 應用的對象) 是否 instanceof 指定的 Type or id 的類型 | | 1 ~ 多個 Type or Id | args(Type or Id, ...) | 指定 Mehtod、Constructor 的參數類型 | | BooleanExpression | if(BooleanExpression) | 滿足指定表達式的 Join Point,表達式中只能使用靜態屬性、Point cuts or advice 暴露的參數、thisJoinPoint 對象 | ```java= @Aspect public class UseWithinCode { @Pointcut("!withincode(com.alien.demo_aspectj.StudentBean.new())") public void newStudentObj() { Log.d("TEST123", "UseWithinCode#newStudentObj !!"); } // 不使用 new 的 set 方式 @Around("set(* com.alien.demo_aspectj.StudentBean.age) && newStudentObj()") public void hookSetNameField(ProceedingJoinPoint point) { Log.d("TEST123", "UseWithinCode#hookSetNameField: " + point.getTarget().toString() + "#" + point.getSignature().getName()); } } ``` ### PointCut - 多條件運算 * PointCut 可以透過 `!`、`&&`、`||` 建立多個條件運算,找到最終需要的結果 1. 原來的程式 ```java= package com.alien.plugin_aspect_j.point_cut; public class TestBean { private final String s; private final int i; public TestBean(String s, int i) { this.s = s; this.i = i; } public TestBean(String s) { this(s, -1); } } ``` 2. 切入指定 Constructor ```java= // 切面 @Aspect public class MultiPointCut { // 切點 1,指定建構式 // Join Point -> execution @Pointcut("execution(com.alien.plugin_aspect_j.point_cut.TestBean.new(..))") public void cutInstanceStudent() { } // 切點 2,指定參數 @Pointcut("args(java.lang.String)") public void cutSpecialArgs() { } // 必須符合 切點 1、2 @Around("cutInstanceStudent() && cutSpecialArgs()") public void hookNewStudent() { } } ``` 透過 Javac 編譯過後,可以看到 AspectJ 只會切入我們指定的 Constructor,而不會切入我們不需要的 Constructor > ![](https://i.imgur.com/DhIY4kH.png) ### Advice 系列 - 處理 PointCut 註解 * 在上面的 PointCut 之後,需要一個處理 PointCut 的函數,這時可以使用 Advice 系列註解, **Advice 可以理解為通知**,用來指定插入 Pointcuts 的哪個位子 | Advice | 說明 | | -------- | -------- | | @Before | Pointcuts 之前 | | @After | Pointcuts 之後,**包括 return、throw 異常** | | @AfterReturning | Pointcuts **正常返回** 才會觸發(不指定類刑責是所有類型) | | @AfterThrowing | 只有在 Pointcuts 拋出異常才會觸發 (不指定類刑責是所有類型) | | @Around | 代替原有的切入點 | * 以下使用常用的 `Around` 介紹 ```java= // 偽代碼 // // 切入面 @AspectJ public class myCutClz { // 切入點 @Around(* *..*.myClz.originalFunction()) public void myCut() { Log.e("TEST", "myCut"); } } public class myClz { public static void main(String[] args) { new myClz().originalFunction(); } void originalFunction() { System.out.println("Hello"); } } // ------------------------------------------- // 偽代碼 // // java -> class 後的狀態 // 使用 execution 的概念,進到 originalFunction 函數後,才開始進行插入 void originalFunction() { beforeExe(); System.out.println("Hello"); afterExe(); } // ------------------------------------------- // 偽代碼 // // 使用 call 的概念 public static void main(String[] args) { new myClz().hookByExecution(); } void hookByExecution() { // 偽代碼 beforeCall(); originalFunction(); afterCall(); } void originalFunction() { System.out.println("Hello"); } ``` > ![](https://i.imgur.com/NMWlQqJ.png) * 使用 Advice 註解的方法,有幾個 **==限制==** 要注意 1. 必須為 **==public== 方法** 2. ++Before++、++After++、++AfterReturning++、++AfterThrowing++ **必須返回 ==void==** 3. 使用 ++**Around**++ 則不能使跟其他 Advice 系列一起使用 > eg. 聲明 Around 後,如果使用 Before、After 則會失敗EnclosingStaticPart 4. 可使用 JoinPoint、ProceedingJoinPoint、JoinPoint.EnclosingStaticPart 作為 **該函數的入參** * 針對 Advice 的入參進行解釋 | 入參 | 說明 | | -------- | -------- | | JoinPoint | 取得 方法簽名 (getSignature)、類型 (getKind)、切入點類名 & 行數 (getSourceLocation),連接靜態訊息 + 動態信息 | | ProceedingJoinPoint | | | EnclosingStaticPart | 連接靜態訊息、連接上下文 | ## Join Point 實做 都會使用以下程式 ```java= public class AspectJActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_aspect_jactivity); call(); execution(); } private void call() { System.out.println("call"); } private void execution() { System.out.println("execution"); } } ``` ### Join Point Method - call 1. Join Point 使用 **call**,並搭配 Advice 的 `Before`、`After`、`AfterReturning`、`AfterThrowing` 一起使用 (暫不使用 @Around,因為會衝突) ```java= // 不要忘了切面 @Aspect @Aspect public class PointCut_execution { // 切點 // 切入 com.alien.demo_aspectj.AspectJActivity 類的 call 函數 // 並且不規定 args 長度 @Pointcut("call(void com.alien.demo_aspectj.AspectJActivity.call(..))") public void cutMethod() { } @Before("cutMethod()") public void adviceWithBefore() { Log.i("Yoyo123", "call, Before ~~~~ !"); } @After("cutMethod()") public void adviceWithAfter() { Log.i("Yoyo123", "call, After ~~~~ !"); } @AfterReturning("cutMethod()") public void adviceWithAfterReturning() { Log.i("Yoyo123", "call, AfterReturning ~~~~ !"); } @AfterThrowing("cutMethod()") public void adviceWithAfterThrowing() { Log.i("Yoyo123", "call, AfterThrowing ~~~~ !"); } } ``` * AspectActivity.java 編譯過後的 class,可以看到其中使用到了許多 try catch,這是為了符合各種狀況 1. PointCut:使用 **call 作為切入點**,所以 **插入的程式都會在 call 之前** 2. Before:會在切入點執行前執行 3. After:無論成功還是拋出都會執行 4. AfterReturning:成功執行 5. AfterThrowing:執行函數錯誤時 (**並看的出來,它並不能阻止 crush,只能捕捉 cr ush 之前**) ```java= public class AspectJActivity extends AppCompatActivity { public AspectJActivity() { } protected void onCreate(Bundle savedInstanceState) { JoinPoint var4 = Factory.makeJP(ajc$tjp_0, this, this, savedInstanceState); try { // @Before HookOnCreate.aspectOf().beforeOnCreate(var4); super.onCreate(savedInstanceState); this.setContentView(2131427356); AspectJActivity var10000 = this; try { try { // @Before PointCut_call.aspectOf().adviceWithBefore(); var10000.call(); } catch (Throwable var7) { // @After PointCut_call.aspectOf().adviceWithAfter(); throw var7; } // @After PointCut_call.aspectOf().adviceWithAfter(); // @AfterReturning PointCut_call.aspectOf().adviceWithAfterReturning(); } catch (Throwable var8) { // @AfterThrowing PointCut_call.aspectOf().adviceWithAfterThrowing(); throw var8; } this.execution(); } catch (Throwable var9) { // @After HookOnCreate.aspectOf().afterOnCreate(); throw var9; } // @After HookOnCreate.aspectOf().afterOnCreate(); } private void println() { System.out.println("TEST"); } static { ajc$preClinit(); } } ``` > ![](https://i.imgur.com/dNgYBgO.png) 2. 使用 JoinPoint call 配合 `@Around` 作為切點 ```java= @Aspect public class PointCut_call_around { @Pointcut("call(void *..*.AspectJActivity.call_around(..))") public void cutMethod() { } @Around("cutMethod()") public void adviceWithAround() { Log.i("Yoyo123", "call, Around ~~~~ !"); } } ``` 編譯過後的 class 檔案 > ![](https://i.imgur.com/d3LRC8E.png) :::warning * Around 不可以跟其他 Advice 一起使用 ::: ### Join Point Method - execution * Join Point 使用 **execution**,並搭配 Advice 的 `Before`、`After`、`AfterReturning`、`AfterThrowing` 一起使用 (暫不使用 @Around,因為會衝突) ```java= @Aspect public class PointCut_execution { // 切點 // 切入 com.alien.demo_aspectj.AspectJActivity 類的 execution 函數 // 並且不規定 args 長度 @Pointcut("execution(void com.alien.demo_aspectj.AspectJActivity.execution(..))") public void cutMethod() { } @Before("cutMethod()") public void adviceWithBefore() { Log.i("Yoyo123", "execution, Before ~~~~ !"); } @After("cutMethod()") public void adviceWithAfter() { Log.i("Yoyo123", "execution, After ~~~~ !"); } @AfterReturning("cutMethod()") public void adviceWithAfterReturning() { Log.i("Yoyo123", "execution, AfterReturning ~~~~ !"); } @AfterThrowing("cutMethod()") public void adviceWithAfterThrowing() { Log.i("Yoyo123", "execution, AfterThrowing ~~~~ !"); } } ``` * AspectActivity.java 編譯過後的 class,可以看到其中使用到了許多 try catch,這是為了符合各種狀況 1. PointCut:使用 **execution 作為切入點**,所以 **插入的程式都會在 execution 之前** 2. Before:會在切入點執行前執行 3. After:無論成功還是拋出都會執行 4. AfterReturning:成功執行 5. AfterThrowing:執行函數錯誤時 (**並看的出來,它並不能阻止 crush,只能捕捉 cr ush 之前**) ```java= public class AspectJActivity extends AppCompatActivity { public AspectJActivity() { } protected void onCreate(Bundle savedInstanceState) { ... 省略部分 } private void call() { System.out.println("call"); } private void execution() { try { try { // @Before PointCut_execution.aspectOf().adviceWithBefore(); System.out.println("execution"); } catch (Throwable var3) { // @After PointCut_execution.aspectOf().adviceWithAfter(); throw var3; } // @After PointCut_execution.aspectOf().adviceWithAfter(); // @AfterReturning PointCut_execution.aspectOf().adviceWithAfterReturning(); } catch (Throwable var4) { // @AfterThrowing PointCut_execution.aspectOf().adviceWithAfterThrowing(); throw var4; } } static { ajc$preClinit(); } } ``` > ![](https://i.imgur.com/oaG6rsy.png) 2. 使用 JoinPoint execution 配合 `@Around` 作為切點 ```java= @Aspect public class PointCut_execution_around { @Pointcut("execution(void *..*.AspectJActivity.execution_around(..))") public void cutMethod() { } @Around("cutMethod()") public void adviceWithAround() { Log.i("Yoyo123", "execution, Around ~~~~ !"); } } ``` 編譯過後的 class 檔案 > ![](https://i.imgur.com/GPbYX22.png) ### Join Point Constructor - call、execution ```java= public class AspectJBean_1 { private final long id; public AspectJBean_1(long id) { this.id = id; } } ``` :::success * 複習一下 1. AspectJ 的 JoinPoint * call(ConstructorPattern) * execution(ConstructorPattern) 2. ConstructorPattern 規則 | 抓取點 | Pattern | 規則 (**參數之間要有空格**) | | -------- | -------- | -------- | | Constructor | ConstructorPattern | [@註解] [訪問權限] [類名.]<new(參數)> [throws 類型] | ::: * 假設我們要監看 `AspectJBean_1` 建構函數可以有以下兩種做法 ```java= @Aspect public class PointCut_around_construct { // 注意 AspectJ 的語法 // 作法 1 call @Pointcut("call(*..*.AspectJBean_1.new(..))") public void cutCallConstruct() { } @Around("cutCallConstruct()") public Object adviceWithAround_1(ProceedingJoinPoint point) throws Throwable { Log.i("Yoyo123", "call, Around ~~~~ !"); return point.proceed(point.getArgs()); } // ------------------------------------------------------------------------------------ // 注意 AspectJ 的語法 // 作法 2 execution @Pointcut("execution(*..*.AspectJBean_1.new(..))") public void cutExecutionConstruct() { } @Around("cutExecutionConstruct()") public Object adviceWithAround_2(ProceedingJoinPoint point) throws Throwable { Log.i("Yoyo123", "call, Around ~~~~ !"); return point.proceed(point.getArgs()); } } ``` * 以上都是透過 Advice 的 `@Around` 做切入 1. 配合 JoinPoint#call 切入,會看到它出現在創建 new 該類的時候被插入 > ![](https://i.imgur.com/76PITbW.png) 2. 配合 JoinPoint#execution 切入,會看到它出現在 該類的 construct 之中 > ![](https://i.imgur.com/etTJa8y.png) :::danger * Constructor 的返回值 處理 PointCut 的函數 (如上面的 adviceWithAround_1、adviceWithAround_2) 必須要返回該類,這裡可以使用 Object 來替代所有類 ! ::: ### Join Point Static ```java= public class AspectJBean_1 { private final long id; private static final List<String> BASE_INFO = new ArrayList<>(); static { BASE_INFO.add("Hello"); BASE_INFO.add("Aspect-J"); } public AspectJBean_1(long id) { this.id = id; } } ``` :::success * 複習一下 1. AspectJ 的 JoinPoint * staticinitialization(TypePattern) 2. ConstructorPattern 規則 | 抓取點 | Pattern | 規則 (**參數之間要有空格**) | | -------- | -------- | -------- | | TypePattern | ConstructorPattern | **指定類** | ::: * 針對 AspectJBean_1 的 Static 模塊的初始化插入 ```java= @Aspect public class PointCut_around_static { // 注意 AspectJ 的語法 @Pointcut("staticinitialization(*..*.AspectJBean_1)") public void cutStatic() { } @Around("cutStatic()") public Object adviceWithAround(ProceedingJoinPoint point) throws Throwable { Log.i("Yoyo123", "adviceWithAround ~~~~ !"); return point.proceed(point.getArgs()); } } ``` > ![](https://i.imgur.com/DIlXgsX.png) ### Join Point Field ```java= public class AspectJBean_1 { public String name; private int phone; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPhone() { return phone; } public void setPhone(int phone) { this.phone = phone; } } ``` :::success * 複習一下 1. AspectJ 的 JoinPoint * get(FieldPattern) * set(FieldPattern) 2. ConstructorPattern 規則 | 抓取點 | Pattern | 規則 (**參數之間要有空格**) | | -------- | -------- | -------- | | Field | FieldPattern | [@註解] [訪問權限] <變量類型> [類名.]<變量名> [throws 類型] | ::: * 我們可以指定某個類中被 Set、Get Field 的時機做插入 ```java= @Aspect public class PointCut_around_field { // 注意 AspectJ 的語法 @Pointcut("get(* *..*.AspectJBean_1.name)") // 取得 Field name public void cutGet() { } @Around("cutGet()") public Object adviceWithAround_1(ProceedingJoinPoint point) throws Throwable { Log.i("cutGet", "Get name, adviceWithAround ~~~~ !"); return point.proceed(point.getArgs()); } // ------------------------------------------------------------------------------ // 注意 AspectJ 的語法 @Pointcut("set(* *..*.AspectJBean_1.phone)") // 設定 Field phone public void cutSet() { } @Around("cutSet()") public Object adviceWithAround_2(ProceedingJoinPoint point) throws Throwable { Log.i("Yoyo123", "Set phone, adviceWithAround ~~~~ !"); return point.proceed(point.getArgs()); } } ``` 1. 取得 AspectJBean_1#name 參數時插入 > ![](https://i.imgur.com/vQTkG4a.png) 2. 取得 AspectJBean_1#phone 參數時插入 > ![](https://i.imgur.com/v9nFu8u.png) * 這邊特別把 name 設定成 public,查看如果不透過 getName 是否也可以正常插入,結果是也可以正常插入的 1. Source 程式 ```java= private void ttt() { AspectJBean_1 aspectJBean_1 = new AspectJBean_1(9527); System.out.println("aspectJBean_1.name" + aspectJBean_1.name); } ``` 2. Class 程式 > ![](https://i.imgur.com/gtwVoNN.png) ### Join Point Handler ```java= public class AspectJBean_1 { public String name; private int phone; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPhone() { return phone; } public void setPhone(int phone) { this.phone = phone; } } ``` :::success * 複習一下 1. AspectJ 的 JoinPoint * handler(TypePattern) 2. ConstructorPattern 規則 | 抓取點 | Pattern | 規則 (**參數之間要有空格**) | | -------- | -------- | -------- | | TypePattern | ConstructorPattern | **指定類** | ::: //TODO: ## Aspect 實作 - 注意點 在實作過程中我們常常會遇到一些問題,可以能是無法編譯,編譯過後的檔案錯誤等等的問題,所以這邊做個經驗總結 ### 檢查 build 訊息 * 檢查自己寫的 Pattern 是否有問題 1. 這是我們要編入的程式 ```java= public class TestBean { private final String s; private final int i; public TestBean(String s, int i) { this.s = s; this.i = i; } public TestBean(String s) { this(s, -1); } } ``` 2. 我們寫的 AspectJ 檔案 ```java= @Aspect public class MultiPointCut { // Join Point -> execution // 只寫了 ConstructorPattern 但沒寫到 Join Point @Pointcut("com.alien.plugin_aspect_j.point_cut.TestBean.new(..)") public void cutInstance() { } @Pointcut("args(java.lang.String)") public void cutSpecialArgs() { } @Around("cutInstance() && cutSpecialArgs()") public void hookNewStudent() { } } ``` cutInstance 只寫了 ConstructorPattern 但沒寫到 Join Point,這時 AspectJ 就無法編入,我們可以透過 Build 訊息察看到它錯誤訊息 (但仍正常編譯成功) > ![](https://i.imgur.com/9zDA1BO.png) ### 檢查 Javac 編譯後的 Class 檔案 * 按下 Build 之後,編譯器會幫我們執行 javac 指令,讓我們寫的 java 檔案編譯成 class,在壓縮到 dex 中,最終形成 APK 安裝 * javac 編譯過後的 class 檔案存在 `build/intermediates/javac` 路徑下,對應你寫的程式的資料夾 > ![](https://i.imgur.com/sRjCzFV.png) * 如上面 MultiPointCut 的範例 1. AspectJ 編入失敗 (其實就啥都沒) > ![](https://i.imgur.com/FQj4XcH.png) 2. AspectJ 編入成功 > ![](https://i.imgur.com/VR60Q3e.png) ### Java Process 運行中問題 * 個人在使用 Android studio 編譯時,時常碰到 **R.jar 正在被占用中 (或是其他 jar 檔案被占用)**,導致無法正常編譯 > ![](https://i.imgur.com/Ko8PdSi.png) * 這時可以透過指令去關閉 Java.exe (**Window 環境**) 1. 尋找 java.exe 的 PID 號 ```shell= tasklist | findstr java* ``` > ![](https://i.imgur.com/IYMrNn3.png) 2. 透過 PID kill java.exe ```shell= taskkill /F /PID 18640 ``` > ![](https://i.imgur.com/6em1rw8.png) * 或是更懶一點~ 直接刪除所有運行的 java.exe Process (不用擔心 IDE 會出問題,當你需要編譯時它會自己啟用 java.exe) ```shell= taskkill /F /FI "IMAGENAME eq java*" ``` > ![](https://i.imgur.com/uC4oCq6.png) ### AspectJ & FireBase crush 衝突 * 實作時有發現一個狀況,AspectJ 的設定 (gradle 中) 會導致 Firebase 無法收到 crush 報告,**這時需要將 firebase 相關 plugin 移到 gradle 最下方** ```groovy= // 原本的位子在最上方 //apply plugin: 'com.google.gms.google-services' //apply plugin: 'com.google.firebase.crashlytics' // 省略部分 import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main final def log = project.logger final def variants = project.android.applicationVariants variants.all { variant -> if (!variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") return } JavaCompile javaCompile = variant.javaCompileProvider.get() javaCompile.doLast { String[] args = ["-showWeaveInfo", "-1.8", // java1.8 "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] log.debug "ajc args: " + Arrays.toString(args) def buildType = variant.buildType.name String[] kotlinArgs = ["-showWeaveInfo", "-1.8", "-inpath", project.buildDir.path + "/tmp/kotlin-classes/" + buildType, "-aspectpath", javaCompile.classpath.asPath, "-d", project.buildDir.path + "/tmp/kotlin-classes/" + buildType, "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] MessageHandler handler = new MessageHandler(true) new Main().run(args, handler) new Main().run(kotlinArgs, handler) for (IMessage message : handler.getMessages(null, true)) { switch (message.getKind()) { case IMessage.ABORT: case IMessage.ERROR: case IMessage.FAIL: log.error message.message, message.thrown break case IMessage.WARNING: log.warn message.message, message.thrown break case IMessage.INFO: log.info message.message, message.thrown break case IMessage.DEBUG: log.debug message.message, message.thrown break } } } } // 移到最下方 apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' ``` ## Appendix & FAQ :::info https://juejin.cn/post/6844903941054922760 ::: ###### tags: `Android 第三方庫`