AspectJX基于AspectJ并在此基础上扩展出来可应用于Android开发平台的AOP框架,可作用于java源码,class文件及jar包,同时支持kotlin的应用。
引入
- 插件引用
在项目根目录的build.gradle里依赖AspectJX
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
}
或者使用product目录下的jar包,在你的项目根目录下新建目录plugins,把product/gradle-android-plugin-aspectjx-2.0.0.jar拷贝到plugins,依赖jar包
dependencies {
classpath fileTree(dir:'plugins', include:['*.jar'])
}
- 在app项目的build.gradle里应用插件
apply plugin: 'android-aspectjx'
//或者这样也可以
apply plugin: 'com.hujiang.android-aspectjx'
AOP可以做什么
- 日志
- 持久化
- 性能监控
- 数据校验
- 缓存
AspectJ核心语法简介
AspectJ 其实就是一种 AOP 框架,AOP 是实现程序功能统一维护的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时大大提高了开发效率。因此 AOP 的优势可总结为如下 两点:
- 无侵入性。
- 修改方便。
下面介绍一下AspectJ的一些核心概念
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理
- 切面(
Aspect
):对横切关注点的抽象 - 连接点(
JoinPoint
):是程序的关键执行点,也是我们关注的点,它是指被拦截到的点(方法、字段、构造器等) - 切入点(
PointCut
):对JoinPoint
进行拦截的定义。PointCut的目的是提供一种方法使得开发者能够选择自己感兴趣的JoinPoint
。 - 通知(
Advice
):切入点仅用于捕捉连接点集合,但是,除了捕捉连接点集合以外什么事情都没有做。事实上实现横切行为我们要使用通知。它 一般指拦截到JoinPoint
后要执行的代码,分为 前置、后置、环绕 三种类型。这里,我们需要注意Advice Precedence
(优先权) 的情况,比如我们对同一个切面方法同时使用了@Before
和@Around
时就会报错,此时会提示需要设置Advice
的优先级。
AspectJ
作为一种基于 Java
语言实现的一套面向切面程序设计规范。它向 Java
中加入了 连接点(Join Point
) 这个新概念,其实它也只是现存的一个 Java 概念的名称而已。它向 Java
语言中加入了少许新结构,譬如切入点(pointcut
)、通知(Advice
)、类型间声明(Inter-type declaration
) 和切面(Aspect
)。切入点和通知动态地影响程序流程,类型间声明则是静态的影响程序的类等级结构,而切面则是对所有这些新结构的封装。
对于AsepctJ
中的各个核心概念来说,其连接点就恰如程序流中适当的一点。而切入点收集特定的连接点集合和在这些点中的值。一个通知则是当一个连接点到达时执行的代码,这些都是 AspectJ
的动态部分。其实连接点就好比是程序中那一条一条的语句,而切入点就是特定一条语句处设置的一个断点,它收集了断点处程序栈的信息,而通知就是在这个断点前后想要加入的程序代码。
此外,AspectJ
中也有许多不同种类的类型间声明,这就允许程序员修改程序的静态结构、名称、类的成员以及类之间的关系。 AspectJ
中的切面是横切关注点的模块单元。它们的行为与 Java
语言中的类很象,但是切面 还封装了切入点、通知以及类型间声明。
AspectJ的语法
- JoinPoint
一般定位在如下位置:
(1)函数调用
(2)获取、设置变量
(3)类初始化
使用PointCut
对我们指定的连接点进行拦截,通过Advice
,就可以拦截到JoinPoint
后要执行的代码。Advice
通常有以下 三种类型:
(1)Before
:PointCut
之前执行。
(2)After
:PointCut
之后执行。
(3)Around
:PointCut
之前、之后分别执行。
示例1,最简单使用:
@Before("execution(* android.app.Activity.on**(..))")
public void onActivityCalled(JoinPoint joinPoint) throws Throwable {
Log.d(...)
}
其中,在 execution 中的是一个匹配规则,第一个 * 代表匹配任意的方法返回值,后面的语法代码匹配所有 Activity 中以 on 开头的方法。这样,我们就可以 在 App 中所有 Activity 中以 on 开头的方法中输出一句log。
上面的 execution 就是处理 Join Point 的类型,通常有如下两种类型:
(1)call:代表调用方法的位置,插入在函数体外面。
(2)execution:代表方法执行的位置,插入在函数体内部。
示例2,统计 Application 中的所有方法耗时:
@Aspect
public class ApplicationAop {
@Around("call (* com.json.chao.application.BaseApplication.**(..))")
public void getTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.i(TAG, name + " cost" + (System.currentTimeMillis() - time));
}
}
需要注意的是,当Advice为Before或After时,函数参数为JoinPoint,当为Around时,函数参数为ProceedingJoinPoint。而Around和Before及After最大的区别是,ProceedingJoinPoint不同于JoinPoint,其内部提供了proceed方法来执行目标函数。