子曰:温故而知新,可以为师矣。 《论语》-- 孔子
一、概念
-
AOP
为Aspect Oriented Programming
的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
二、AspectJ
1. 介绍:
- 面向切面编程的框架,是对 Java 的扩展,而且完全兼容 java 。它定义了 AOP 语法,有一个专门的编译器用来生成遵守 Java 字节码编码规范的 Class 文件, 还支持原生的 Java,只需要加上 AspectJ 提供的注解即可。
2. 术语
-
Joinpoint
(连接点):指那些被拦截到的点。 -
PointCut
(切入点):指我们要对哪些 Joinpoint 进行拦截的定义。 -
Advice
(通知/增强):值拦截到 Joinpoint 之后需要做的事情。 -
Introduction
(引介):一种特殊的通知,在不修改类代码的前提下。 -
Target
(目标对象):代理的目标对象。 -
Weaving
(织入):把增强应用到目标对象来创建新的代理对象的过程。 -
Proxy
(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。 -
Aspect
(切面):是切入点和通知(引介)的结合。
3. 实操
1. 根目录以及项目的 .gradle
文件添加依赖。
// 1. 根目录 .gradle文件
classpath 'org.aspectj:aspectjtools:1.9.4'
// 2. 项目 .gradle文件
implementation 'org.aspectj:aspectjrt:1.9.4'
// 注意 minSdkVersion 24
// 3. 项目 .gradle文件 最后添加以下代码,用于打印日志。
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.9",
"-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;
}
}
}
}
2. 自定义注解
// 用户登录检测 LoginCheck 类
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCheck {
}
// 用户点击痕迹(行为统计) ClickBehavior 类
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickBehavior {
String value();
}
3. 方法上加上自定义注解
// 登录点击事件(用户行为统计)
@ClickBehavior("登录")
public void login(View view) {
Log.e(TAG, "模拟接口请求……验证通过,登录成功!");
}
// 用户行为统计(友盟统计?!后台要求自己统计)
@ClickBehavior("我的专区")
@LoginCheck
public void area(View view) {
Log.e(TAG, "开始跳转到 -> 我的专区 Activity");
startActivity(new Intent(this, OtherActivity.class));
}
4. 编写 Aspect 界面
Aspect // 定义切面类
public class ClickBehaviorAspect {
private final static String TAG = "TAG";
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution,以方法执行时作为切点,触发Aspect类
// * *(..)) 可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@com.kww.aopdmo.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;
}
}
@Aspect // 定义切面类
public class LoginCheckAspect {
private final static String TAG = "TAG";
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution,以方法执行时作为切点,触发Aspect类
// * *(..)) 可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@com.kww.aopdmo.LoginCheck * *(..))")
public void methodPointCut() {}
// 2、对切入点如何处理
@Around("methodPointCut()")
public Object jointPotin(ProceedingJoinPoint joinPoint) throws Throwable {
Context context = (Context) joinPoint.getThis();
if (true) { // 从SharedPreferences中读取
Log.e(TAG, "检测到已登录!");
return joinPoint.proceed();
} else {
Log.e(TAG, "检测到未登录!");
Toast.makeText(context, "请先登录!", Toast.LENGTH_SHORT).show();
context.startActivity(new Intent(context, LoginActivity.class));
return null; // 不再执行方法(切入点)
}
}
}
写在文末
纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游
至此,AOP
的使用就说完了,各位看官食用愉快。
码字不易,如果本篇文章对您哪怕有一点点帮助,请不要吝啬您的点赞,我将持续带来更多优质文章。