---
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() {
}
}
```
> 
* 使用 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");
}
```
> 
### 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
> 
### 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");
}
```
> 
* 使用 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();
}
}
```
> 
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 檔案
> 
:::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();
}
}
```
> 
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 檔案
> 
### 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 該類的時候被插入
> 
2. 配合 JoinPoint#execution 切入,會看到它出現在 該類的 construct 之中
> 
:::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());
}
}
```
> 
### 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 參數時插入
> 
2. 取得 AspectJBean_1#phone 參數時插入
> 
* 這邊特別把 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 程式
> 
### 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 訊息察看到它錯誤訊息 (但仍正常編譯成功)
> 
### 檢查 Javac 編譯後的 Class 檔案
* 按下 Build 之後,編譯器會幫我們執行 javac 指令,讓我們寫的 java 檔案編譯成 class,在壓縮到 dex 中,最終形成 APK 安裝
* javac 編譯過後的 class 檔案存在 `build/intermediates/javac` 路徑下,對應你寫的程式的資料夾
> 
* 如上面 MultiPointCut 的範例
1. AspectJ 編入失敗 (其實就啥都沒)
> 
2. AspectJ 編入成功
> 
### Java Process 運行中問題
* 個人在使用 Android studio 編譯時,時常碰到 **R.jar 正在被占用中 (或是其他 jar 檔案被占用)**,導致無法正常編譯
> 
* 這時可以透過指令去關閉 Java.exe (**Window 環境**)
1. 尋找 java.exe 的 PID 號
```shell=
tasklist | findstr java*
```
> 
2. 透過 PID kill java.exe
```shell=
taskkill /F /PID 18640
```
> 
* 或是更懶一點~ 直接刪除所有運行的 java.exe Process (不用擔心 IDE 會出問題,當你需要編譯時它會自己啟用 java.exe)
```shell=
taskkill /F /FI "IMAGENAME eq java*"
```
> 
### 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 第三方庫`