AOP 简介
背景
在软件开发中,特别是在面向对象编程中,我们常常会面临一些横切关注点的问题。这些横切关注点可能涉及到日志记录、性能监控、安全检查等,它们通常会分散在应用程序的多个模块中,导致代码重复、耦合性增加等问题。为了解决这些问题,面向切面编程(Aspect-Oriented Programming,AOP)应运而生。
Aspectj
AspectJ 是一个基于 Java 语言的 AOP 框架,它提供了一种简洁的方式来处理横切关注点。通过 AspectJ,我们可以将横切关注点模块化,并且可以在不修改原始代码的情况下,将这些关注点织入到应用程序的特定点上。
AspectJX (不支持 AGP 8)
一个基于AspectJ并在此基础上扩展出来可应用于Android开发平台的AOP框架,可作用于java源码,class文件及jar包,同时支持kotlin的应用。
常规应用场景
AOP 可以应用于许多常见的场景,包括但不限于:
- 网络检测:在网络请求前后记录日志、监控网络状态。
- 登录检测:在某些方法执行前检查用户是否已登录,如果未登录则进行相应处理。
- 多重点击检测:防止用户在短时间内多次点击按钮,导致重复操作。
- 日志打印:在方法执行前后记录日志,用于调试和性能监控。
- 权限检查:在方法执行前检查用户是否有权限执行该操作。
使用方法
依赖导入
在项目的根目录下 build.gradle 文件中添加 AspectJ 的依赖:
buildscript {
dependencies {
……
// AOP 配置插件:https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
……
}
}
在项目的 app 模块目录下 build.gradle 文件中添加 AspectJ 的依赖:
apply plugin: 'android-aspectjx'
android {
// AOP 配置(exclude 和 include 二选一)
// 需要进行配置,否则就会引发冲突,具体表现为:
// 第一种:编译不过去,报错:java.util.zip.ZipException:Cause: zip file is empty
// 第二种:编译能过去,但运行时报错:ClassNotFoundException: Didn't find class on path: DexPathList
aspectjx {
// 排除一些第三方库的包名(Gson、 LeakCanary 和 AOP 有冲突)
// exclude 'androidx', 'com.google', 'com.squareup', 'org.apache', 'com.alipay', 'com.taobao', 'versions.9'
// 只对以下包名做 AOP 处理
include android.defaultConfig.applicationId
}
dependencies {
// AOP 插件库:https://mvnrepository.com/artifact/org.aspectj/aspectjrt
implementation 'org.aspectj:aspectjrt:1.9.6'
}
}
定义注解
例如下面的 @Log
注解用于标记需要记录日志的方法,分别在方法执行前后做对应日志处理:
/**
* desc : Debug 日志注解
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.CONSTRUCTOR)
annotation class Log constructor(val value: String = "AppLog")
应用注解
直接在方法、构造方法,getter setter上应用注解即可
class MyClass {
@Log("MyClass")
fun myMethod() {
// Method body
}
}
处理注解的切面类
编写切面类,处理注解所标记的方法:
/**
* desc : Debug 日志切面
*/
@Aspect
class LogAspect {
/**
* 构造方法切入点
*/
@Pointcut("execution(@top.xlxs.scaffold.aop.Log *.new(..))")
fun constructor() {}
/**
* 方法切入点
*/
@Pointcut("execution(@top.xlxs.scaffold.aop.Log * *(..))")
fun method() {}
/**
* 在连接点进行方法替换
*/
@Around("(method() || constructor()) && @annotation(log)")
@Throws(Throwable::class)
fun aroundJoinPoint(joinPoint: ProceedingJoinPoint, log: Log): Any? {
enterMethod(joinPoint, log)
val startNanos: Long = System.nanoTime()
val result: Any? = joinPoint.proceed()
val stopNanos: Long = System.nanoTime()
exitMethod(joinPoint, log, result, TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos))
return result
}
/**
* 方法执行前切入
*/
private fun enterMethod(joinPoint: ProceedingJoinPoint, log: Log) {
val codeSignature: CodeSignature = joinPoint.signature as CodeSignature
// 方法所在类
val className: String = codeSignature.declaringType.name
// 方法名
val methodName: String = codeSignature.name
// 方法参数名集合
val parameterNames: Array<String?> = codeSignature.parameterNames
// 方法参数值集合
val parameterValues: Array<Any?> = joinPoint.args
//记录并打印方法的信息
val builder: StringBuilder =
getMethodLogInfo(className, methodName, parameterNames, parameterValues)
log(log.value, builder.toString())
val section: String = builder.substring(2)
Trace.beginSection(section)
}
/**
* 获取方法的日志信息
*
* @param className 类名
* @param methodName 方法名
* @param parameterNames 方法参数名集合
* @param parameterValues 方法参数值集合
*/
private fun getMethodLogInfo(className: String, methodName: String, parameterNames: Array<String?>, parameterValues: Array<Any?>): StringBuilder {
val builder: StringBuilder = StringBuilder("\u21E2 ")
builder.append(className)
.append(".")
.append(methodName)
.append('(')
for (i in parameterValues.indices) {
if (i > 0) {
builder.append(", ")
}
builder.append(parameterNames[i]).append('=')
builder.append(parameterValues[i].toString())
}
builder.append(')')
if (Looper.myLooper() != Looper.getMainLooper()) {
builder.append(" [Thread:\"").append(Thread.currentThread().name).append("\"]")
}
return builder
}
/**
* 方法执行完毕,切出
*
* @param result 方法执行后的结果
* @param lengthMillis 执行方法所需要的时间
*/
private fun exitMethod(joinPoint: ProceedingJoinPoint, log: Log, result: Any?, lengthMillis: Long) {
Trace.endSection()
val signature: Signature = joinPoint.signature
val className: String? = signature.declaringType.name
val methodName: String? = signature.name
val builder: StringBuilder = StringBuilder("\u21E0 ")
.append(className)
.append(".")
.append(methodName)
.append(" [")
.append(lengthMillis)
.append("ms]")
// 判断方法是否有返回值
if (signature is MethodSignature && signature.returnType != Void.TYPE) {
builder.append(" = ")
builder.append(result.toString())
}
log(log.value, builder.toString())
}
private fun log(tag: String?, msg: String?) {
Timber.tag(tag)
Timber.d(msg)
}
}
结语
通过 AOP,我们可以更加方便地处理横切关注点,使代码更加清晰、易读,并且提高了代码的复用性和可维护性。在实际项目中,可以根据具体需求,灵活运用 AOP 来解决各种问题,提升软件开发效率和代码质量。