AspectJ+AOP+注解实现用户行为统计
2019-10-01 本文已影响0人
Chenyangqi
本文Demo地址:https://github.com/DaLeiGe/AndroidSamples/tree/master/AspectJAOP
AspectJ概念
AspectJ 是使用最为广泛的 AOP 实现方案
适用于 Java 平台
官网地址:http://www.eclipse.org/aspectj/
AspectJ 是在静态织入代码,即在编译期注入代码的
结合自定义注解实现AOP切面实现用户行为统计
AspectJ使用
只需要关注两个点
- 1:切入点,本案例切入点为自定义注解
- 2:针对切入点的切入行为
举个栗子
要实现统计某个方法的执行次数,在不更改且不增加原有代码逻辑的前提下通过AspectJ和自定义注解的方式实现
AspectJ环境配置
Project glide
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Module glide
apply plugin: 'com.android.application'
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
buildscript { // 编译时用Aspect专门的编译器,不再使用传统的javac
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
android {
compileSdkVersion 28
buildToolsVersion "29.0.0"
defaultConfig {
applicationId "com.cyq.aspectjaop"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
implementation 'org.aspectj:aspectjrt:1.8.13'
}
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
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.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.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)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, 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;
}
}
}
}
自定义注解ClickBehavior,作用在方法之上
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 用户点击痕迹(行为统计) IoC容器
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickBehavior {
String value();
}
在要监听的业务方法上添加注解
// 用户行为统计
@ClickBehavior("测试参数")
public void coupon(View view) {
Log.e(TAG, "开始跳转到 -> 我的优惠券 Activity");
startActivity(new Intent(this, OtherActivity.class));
}
然后就可以使用AspectJ定义切面类ClickBehaviorAspect
这个类有两个功能
- 1:定位注解的位置(切入点)
- 2:对切入点如何处理
import com.cyq.aspectjaop.annotation.ClickBehavior;
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 // 定义切面类
public class ClickBehaviorAspect {
private final static String TAG = "netease >>> ";
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution,以方法执行时作为切点,触发Aspect类
// * *(..)) 可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@com.cyq.aspectjaop.annotation.ClickBehavior * *(..))")
public void methodPointCut() {}
// 2、对切入点如何处理
@Around("methodPointCut()")
public Object jointPotin(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取签名方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取方法所属的类名
String className = methodSignature.getDeclaringType().getSimpleName();
// 获取方法名
String methodName = methodSignature.getName();
// 获取方法的注解值(需要统计的用户行为)
String funName = methodSignature.getMethod().getAnnotation(ClickBehavior.class).value();
// 统计方法的执行时间、统计用户点击某功能行为。(存储到本地,每过x天上传到服务器)
long begin = System.currentTimeMillis();
Log.e(TAG, "ClickBehavior Method Start >>> ");
Object result = joinPoint.proceed(); // MainActivity中切面的方法
long duration = System.currentTimeMillis() - begin;
Log.e(TAG, "ClickBehavior Method End >>> ");
Log.e(TAG, String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",
funName, className, methodName, duration));
return result;
}
}
执行结果如下
2019-10-01 00:13:55.263 10479-10479/com.cyq.aspectjaop E/netease >>>: ClickBehavior Method Start >>>
2019-10-01 00:13:55.263 10479-10479/com.cyq.aspectjaop E/netease >>>: 模拟接口请求……验证通过,登录成功!
2019-10-01 00:13:55.263 10479-10479/com.cyq.aspectjaop E/netease >>>: ClickBehavior Method End >>>
2019-10-01 00:13:55.264 10479-10479/com.cyq.aspectjaop E/netease >>>: 统计了:登录功能,在MainActivity类的login方法,用时0 ms
通过如上方式实现用户行为统计,可以避免更改原有业务逻辑