AspectJ 在 Android 中的使用
在介绍 AspectJ 之前,我们先看看常见的几种编程架构思想。
- 面向对象编程 Object Oriented Programming
- 面向过程编程 Procedure Oriented Programming
- 面向切面编程 Aspect Oriented Programming
面向对象、面向过程、面向切面, 这三种是我们常见的三种编程架构思想,在日常的编程中, OOP 是 Android 开发中最常见的,其他的两种比较少见。
一、AOP
AOP 是面向切面编程,它在我们的日志系统、权限管理方面有着比较好的应用。
在项目中,我们的很多功能都是分散到各个模块,例如日志打印,AOP 的目标就是要把这些功能集中起来,放到一个统一的地方来控制和管理。
二、AspectJ
AOP 是一种编程的思想,在具体的编程中需要实际的工具去实现这套思想。 AspectJ 就是这样的一个工具。使用 AspectJ 有两种方式
-
- 完全使用 AspectJ 的语言开发;
-
- 使用 AspectJ 注解,完全的使用纯 Java 开发
我们后续讲的,基本上都是以 AspectJ 注解的方法,同时在最后也会附上 AspectJ 和 AspectJ 注解的等价。
1.AspectJ 语法
这里只是介绍简单的一些概念,如果想要去了解深入的用法,可参考文后的链接,去官网查看。
1. JoinPoint
JoinPoint: A particular point in a program that might be the target of code injection.
JoinPoint 简单一点说就是程序运行时要执行一些动作的点。
AspectJ 中可以选择的 JoinPoint
JoinPoint | 说明 | 示例 |
---|---|---|
method call | 函数调用 | 例如调用 Log.e( ) |
method execution | 函数执行 | 例如 Log.e( ) 的执行内部。 method call 是调用某个函数的地方 execution 是某个函数执行的内部 |
constructor call | 构造函数调用 | 和 method call 类似 |
constructor execution | 构造函数执行 | 和 method execution 类似 |
field get | 获取某个变量 | 例如读取 MainActivity.mTest 成员 |
field set | 设置某个变量 | 例如设置 MainActivity.mTest 成员 |
pre-initialization | Object 在构造函数中做的一些工作 | |
initialization | Object 在构造函数中做的工作 | |
static initialization | 类初始化 | 例如类的 static{} |
handler | 异常处理 | 例如 try catch(xxx) 中,对应 catch 内的执行 |
advice execution | AspectJ 的内容 |
JoinPoint 的选择要结合下面的 Pointcuts 表达式来看
2. Pointcut
Pointcut: An expression which tell a code injection tool where to inject a particular piece of code
Pointcut 简单的说就是从一堆的 JoinPoint 中挑选感兴趣的 JoinPoint 的表达式。
例如
pointcut anyCall(): call(* *.println(…)) && !within(TestAspect);
在 AspectJ 的语言中定义一个 Pointcout 需要用关键词 pointcut .
上面的这里是
- pointcut: 是定一个 Pointcut 的关键词
- anyCall(): 是 Pointcut 的名称
- call : 表示 JoinPoint 的类型为 call
- 第一个 ‘*’ 号是返回值, ‘*’ 代表是任意返回值; 第二个 ‘*’ 号代表是包名,‘*’ 代表是任意包名,这边表明我们是选择任意包名下的 println 函数;在 (…) 中指定参数类型,‘…’ 通配符表示任意类型;
- &&! 表示组合条件,有 &&, || 以及 !
- within(TestAspect): within 是 JoinPoint 间接选择过滤的一个方法,后面会讲到。 !within(TestAspect) 表示调用者的类型不是 TestAspect.
3. JointPoint 的选择
JointPoint 的选择有分成直接选择和间接选择两种方式
- JointPoint 的直接选择就是通过和 Pointcut 的语法一一对应关系中选择;
- JointPoint 的间接选择就是通过一些通配符进行筛选过滤的选择,上面例子中的 within 就是间接选择的一种。
1.JointPoint 直接选择
JoinPoint 的选择策略和 Pointcut 的语法对应关系
JoinPoint Category | Pointcut Syntax |
---|---|
Method execution | execution(MethodSignature) |
Method call | call(MethodSignature) |
Constructor execution | execution(ConstructorSignature) |
Constructor call | call(ConstructorSignature) |
Class initialization | staticinitialization(TypeSignature) |
Field read access | get(FieldSignature) |
Field write access | set(FieldSignature) |
Exception handler execution | handler(TypeSignature) |
Object initialization | initialization(ConstructorSignature) |
Object pre-initialization | preinitialization(ConstructorSignature) |
Advice execution | adviceexecution() |
JoinPoint 的策略的选择对应着不同 Pointcut,特别是 Pointcut 里面有着不同的 Signature。
以下有详细的说明:
Method Signature 表达式
语法
@注解 访问权限 返回值的类型 包名.函数名(参数)
例子:
@before(“execution(* android.app.Activity.on**(…))”);
-
注解: 是可选项; 这里是 @before,关于注解的在后面 Adivce 中有更详细的说明
-
访问权限: 可选项; 有 public, private, protected 类型;例子没有设置
-
返回值的类型: 与普通函数的返回值类型是一样的,如果不限定类型,用通配符 * 表示。例子中是 *
-
包名.函数名:用于查找匹配的函数,可以使用通配符
通配符的类型- ’ * '表示用于匹配处 . 号之外的任意字符;
- ’ … ’ 表示任意子 package
- ’ + '号表示子类
例子:- java.*.Data: 可以表示 java.sql.Data ,也可以表示 java.util.Date;
- Test* : 表示Test开头的函数,可以表示 TestBase, 也可以表示 TestDervied
- java…* : 表示 java 任意子类
- java…*Model+: 表示 Java 任意 package 中名字以 Model 结尾的子类,比如 TabelModel, TreeModel 等
-
函数参数
参数有不同的型式- (int, char): 表示参数只有两个, 并且第一个参数是 int, 第二个参数是 char;
- (String, …): 表示参数至少有一个。并且第一个参数是 String, 后面参数类型不限。在参数匹配中, … 代表任意参数个数和类型;
- (Oject …): 表示不定个数的参数,并且类型都是 Object, 这里的 … 不是通配符,而是 java 中不定参数的意思;
Constructor Signature 表达式
和 Method Signature 类似
不同点:
构造函数没有返回值,并且函数名必须叫 new
例子:
public *…TestDeived.new(…)
- public: 表示选择 public 访问权限的
- *… : 代表任意包名
- TestDeived.new: 代表 TestDerived 的构造函数
- (…): 代表参数个数和类型都是任意的
Field Signature表达式
语法
@注解 访问权限 类型 类名.成员变量名
- @注解和访问权限是可选的
- 类型:成员变量类型, * 表示任意类型
- 类名.成员变量名: 成员变量名可以是*, 代表任意成员变量
例子, 用 AspectJ 打印成员变量赋值前后的值
// TraceAspect.java set field 的切面
private static final String POINTCUT_FILEED =
"set(int org.android10.viewgroupperformance.activity.MainActivity.mTest) && args(newValue) && target(t)";
@Before(POINTCUT_FILEED)
public void onFiled(JoinPoint joinPoint, Object newValue, Object t) throws IllegalAccessException {
Object object = joinPoint.getThis();
FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature();
String fileName = fieldSignature.getName();
Field field = fieldSignature.getField();
field.setAccessible(true);
Class clazz = fieldSignature.getFieldType();
String clazzName = clazz.getSimpleName();
Object oldValue = field.get(t);
Log.i("MainActivity", "\nonFiled value = " + newValue.toString() + "\n fieldSignature =" + fieldSignature.toString()
+ "\nfield = " + field.toString() + " + \nFileName = " + fileName
+ "\nclazzName = " + clazzName + " \noldValue = " + oldValue.toString() );
}
// 在 MainActivity.java 中
@Override
protected void onResume() {
super.onResume();
mTest = 100;
}
打印结果
onFiled value = 100
fieldSignature =int org.android10.viewgroupperformance.activity.MainActivity.mTest
field = private int org.android10.viewgroupperformance.activity.MainActivity.mTest +
FileName = mTest
clazzName = int
oldValue = -1
TypeSignature表达式
例子:
staticinitlization(test…TestBase): 表示 TestBase 类的 static block
handler(NullPointException): 表示 catch 到 NullPointerException 的 JPoin
2.JointPoint 间接选择
JointPoint 的直接选择是通过 Signature 信息匹配的,除此之外还有其他的方式,这些方式都可以归类到间接选择
关键词 | 说明 | 实例 |
---|---|---|
within(TypePattern) | TypePattern 表示 package 或者类 TypePattern 可以使用通配符 |
表示某个 Package 或者类中的 Point within(Test): Test 类中(包括内部类)所有的 JointPoint |
withcode(Constructor Signature|Method Signature) | 表示某个构造函数或其他函数执行过程涉及到的 JointPoint | withinCode(* Test.testMethod(…)) 表示 testMethod 涉及的 JointPoint withinCode(*.Test.new(…)) 表示 Test 的构造函数涉及的 JointPoint |
cflow(pointcuts) | cflow 表示 call flow cflow 的条件是一个 pointcut |
cflow(call Test.testMethod) 表示调用 Test.testMethod 函数是所包含的 JointPoint,包含 testMethod 的 call 这个 JointPoint 本身 |
cflowbelow(pointcuts) | cflowbelow 表示不包含自身的 cflow | cflowbelow(call Test.testMethod) 表示调用 Test.testMethod 函数是所包含的 JointPoint, 不包含 testMethod 的 call 这个 JointPoint 本身 |
this(Type) | JointPoint 的 this 对象是 Type 类型 | JPoint是代码段(不论是函数,异常处理,static block),从语法上说,它都属于一个类。如果这个类的类型是Type标示的类型,则和它相关的JPoint将全部被选中。 |
target(Type) | JoinPoint 的 target 对象是 Type 类型 | 和this相对的是target。不过target一般用在call的情况。call一个函数,这个函数可能定义在其他类。比如testMethod是TestDerived类定义的。那么target(TestDerived)就会搜索到调用testMethod的地方。但是不包括testMethod的execution JointPoint |
args(TypeSignature) | 用来对 JointPoint 的参数进行条件搜索 | 例如 arg(int, …) 表示第一个参数是 int, 后面参数个数和类型不限的 JointPoint |
3.call 与 execution 区别
当 call 捕获 joinPoint 时,捕获的签名方法的调用点;execution 捕获 joinPoint 时,捕获的则是执行点。
两个的区别在于一个是 ”调用点“, 一个是 ”执行点“
对于 call 来讲
call(Before)
Pointcut {
Pointcut Method
}
call(After)
对于 execution 来说
Pointcut {
Execution(Before)
Pointcut Method
Execution(After)
}
3.AspectJ 注解的等价
AspectJ 提供了相应的注解,注解的方式和 AspectJ 语言编写是等效的。我们在 Android 中一般也是采用注解的方式