文章目录
一、定义 Join Point 连接点注解
要监控哪些方法 , 首先要对该方法添加注解 , 该注解标记哪些方法是 Join Point 连接点 , 所有被该注解标记的方法构成 一组 Join Point 连接点 , 即 Point Cut 切入点 ;
package com.example.aop_demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 该注解标记哪些方法是 Join Point 连接点
* 所有被该注解标记的方法构成 一组 Join Point 连接点 , 即 Point Cut 切入点
*/
@Retention(RetentionPolicy.RUNTIME) // 注解保留到运行时
@Target(ElementType.METHOD) // 注解作用于方法上
public @interface Monitor {
String value();
}
注解的用法如下 : 如果想要监控下面的 public void textClick(View view)
方法 , 在该方法上添加 @Monitor
注解即可 ;
@Monitor("textClick")
public void textClick(View view) {
// 休眠 500 ms
SystemClock.sleep(500);
}
二、定义 Aspect 切面
1、定义 Aspect 切面
Aspect 切面 : Java 中的类声明是 对事物的抽象 , AspectJ 中的 Aspect 切面就是 对切面的抽象 , 其中包含了 Point Cut 切入点 和 Advice 通知 ; 使用 @Aspect
注解修饰 ;
/**
* 定义 Aspect 切面
*/
@Aspect
public class MonitorAspect {
}
2、定义 Aspect 切面
Point Cut 切入点 : 一组 Join Point 连接点 , 通过 逻辑关系 / 正则表达式 / 通配符 等关系组合 , 定义了 Advice 通知发生的位置 ;
解析 "execution(@com.example.aop_demo.Monitor * *(..))"
匹配规则 :
-
@com.example.aop_demo.Monitor
表示带该注解的方法 - 第 1 个
*
表示在所有包下面 - 第 2 个
*
表示在所有类下面 -
(..)
表示所有方法 , 参数不限
整体含义 : 所有的包 所有的类 中 , 带有 @com.example.aop_demo.Monitor
注解的方法 , 都是 Pointcut 切入点 , 每个方法都是一个 JoinPoint 连接点 ;
/**
* 定义切入点
* "execution(@com.example.aop_demo.Monitor * *(..))" 表示
* 带有 @com.example.aop_demo.Monitor 注解的
* 所有包下面的 所有类中的 所有方法, 方法参数不限
* 上述方法组成 切入点 , 每个方法都是一个 Join Point 连接点
*
* execution(@com.example.aop_demo.Monitor * *(..)) 解读
* - @com.example.aop_demo.Monitor 表示带该注解的方法
* - 第 1 个 * 表示在所有包下面
* - 第 2 个 * 表示在所有类下面
* - (..) 表示所有方法 , 参数不限
*
* 所有的包 所有的类 中 , 带有 @com.example.aop_demo.Monitor 注解的方法 , 都是 Pointcut 切入点
* 上述每个方法都是一个 Join Point 连接点
*/
@Pointcut("execution(@com.example.aop_demo.Monitor * *(..))")
public void pointCut(){}
3、逐个处理切入点的各个连接点
@Around("pointCut()")
注解中传入的注解属性是 切入点 的名称 , 就是上面定义的 public void pointCut(){}
方法对应的 Pointcut 切入点 ;
获取 JoinPoint 连接点 对应 方法的相关属性 :
-
获取方法上的注解 , 以及注解属性 : 首先获取方法签名 , 在回去方法签名对应的
Method
对象 , 获取该对象上的注解 , 根据注解调用注解中定义的获取属性的接口方法 ;
// 获取方法上 @Monitor("onClick") 注解中的注解属性 字符串
// 获取被 @Monitor("onClick") 注解修饰的方法的 方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 根据方法签名获取方法
// 然后获取方法上的 @Monitor 注解
Monitor annotation = signature.getMethod().getAnnotation(Monitor.class);
// 获取 @Monitor("onClick") 注解中的注解属性
String value = annotation.value();
-
获取方法名称 :
signature.getDeclaringType()
就是方法所在的类的字节码类对象 , 然后调用getSimpleName
方法即可获取类名 ;
// 获取方法名称
String className = signature.getDeclaringType().getSimpleName();
-
获取方法所在类名称 : 直接调用方法签名的
getName
方法 , 即可获取方法名 ;
// 获取方法所在的类名
String methodName = signature.getName();
调用 joinPoint.proceed()
方法 , 可同步执行该具体的方法 , 方法的上下可以进行用户自己的埋点业务逻辑 , 如统计方法执行耗时等操作 ;
// 执行具体的方法
result = joinPoint.proceed();
代码示例 :
/**
* 逐个处理 Pointcut 切入点 中的 JoinPoint 连接点
* 一个一个处理
*
* @Around("pointCut()") 注解中传入的注解属性是
* 切入点的名称 , 就是上面定义的 public void pointCut(){} 方法
*
* @param joinPoint
* @return
*/
@Around("pointCut()")
public Object processJoinPoint(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
// 获取方法上 @Monitor("onClick") 注解中的注解属性 字符串
// 获取被 @Monitor("onClick") 注解修饰的方法的 方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 根据方法签名获取方法
// 然后获取方法上的 @Monitor 注解
Monitor annotation = signature.getMethod().getAnnotation(Monitor.class);
// 获取 @Monitor("onClick") 注解中的注解属性
String value = annotation.value();
// 获取方法名称
String className = signature.getDeclaringType().getSimpleName();
// 获取方法所在的类名
String methodName = signature.getName();
// 记录方法执行开始时间
long startTime = System.currentTimeMillis();
// 执行具体的方法
result = joinPoint.proceed();
// 记录方法执行结束时间
long endTime = System.currentTimeMillis();
Log.i(TAG, "执行 " + className + " 中的 " + methodName +
" 方法花费了 " + (endTime - startTime) + " ms , 注解属性为 " + value );
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
4、完整 Aspect 切面代码
package com.example.aop_demo;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
/**
* 定义 Aspect 切面
*/
@Aspect
public class MonitorAspect {
private static final String TAG = "MonitorAspect";
/**
* 定义切入点
* "execution(@com.example.aop_demo.Monitor * *(..))" 表示
* 带有 @com.example.aop_demo.Monitor 注解的
* 所有包下面的 所有类中的 所有方法, 方法参数不限
* 上述方法组成 切入点 , 每个方法都是一个 Join Point 连接点
*
* execution(@com.example.aop_demo.Monitor * *(..)) 解读
* - @com.example.aop_demo.Monitor 表示带该注解的方法
* - 第 1 个 * 表示在所有包下面
* - 第 2 个 * 表示在所有类下面
* - (..) 表示所有方法 , 参数不限
*
* 所有的包 所有的类 中 , 带有 @com.example.aop_demo.Monitor 注解的方法 , 都是 Pointcut 切入点
* 上述每个方法都是一个 Join Point 连接点
*/
@Pointcut("execution(@com.example.aop_demo.Monitor * *(..))")
public void pointCut(){}
/**
* 逐个处理 Pointcut 切入点 中的 JoinPoint 连接点
* 一个一个处理
*
* @Around("pointCut()") 注解中传入的注解属性是
* 切入点的名称 , 就是上面定义的 public void pointCut(){} 方法
*
* @param joinPoint
* @return
*/
@Around("pointCut()")
public Object processJoinPoint(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
// 获取方法上 @Monitor("onClick") 注解中的注解属性 字符串
// 获取被 @Monitor("onClick") 注解修饰的方法的 方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 根据方法签名获取方法
// 然后获取方法上的 @Monitor 注解
Monitor annotation = signature.getMethod().getAnnotation(Monitor.class);
// 获取 @Monitor("onClick") 注解中的注解属性
String value = annotation.value();
// 获取方法名称
String className = signature.getDeclaringType().getSimpleName();
// 获取方法所在的类名
String methodName = signature.getName();
// 记录方法执行开始时间
long startTime = System.currentTimeMillis();
// 执行具体的方法
result = joinPoint.proceed();
// 记录方法执行结束时间
long endTime = System.currentTimeMillis();
Log.i(TAG, "执行 " + className + " 中的 " + methodName +
" 方法花费了 " + (endTime - startTime) + " ms , 注解属性为 " + value );
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
三、使用 AspectJ 埋点并监控方法性能
监控 public void textClick(View view)
方法性能 , 在该方法上使用 @Monitor
注解 , 该注解已经被定义为切入点 , 所有的包 所有的类 中 , 带有 @com.example.aop_demo.Monitor
注解的方法 , 都是 Pointcut 切入点 , 应用执行时 , 会自动执行埋点业务逻辑 , 这里是在方法执行前后 , 分别记录时间 , 最终计算出方法的耗时 ;
package com.example.aop_demo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Monitor("textClick")
public void textClick(View view) {
// 休眠 500 ms
SystemClock.sleep(500);
}
}
执行结果 : 点击按钮 , 触发 textClick
方法 , 触发了埋点逻辑 , 自动计算出了该方法的执行耗时 ;
2021-09-22 22:23:04.954 12492-12492/com.example.aop_demo I/MonitorAspect: 执行 MainActivity 中的 textClick 方法花费了 501 ms , 注解属性为 textClick