面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为侧面(aspect,又译作方面)的语言构造为基础,侧面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。侧面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与侧面相关的编程概念还包括元对象协议、主题(subject)、混入(mixin)和委托。(摘自维基百科)
aop是一种面向切面的编程**,目的在于从切面插入自己需要的代码,在不破坏、不耦合原有代码逻辑的基础上,实现相关的功能,在Android中的应用场景非常丰富,如:数据统计、日志记录、用户行为统计、应用性能统计、数据校验、行为拦截,等等应用场景。
- APT(直接插入java文件);
- Aspectj(编织class文件);
- javassist(修改class文件);
aspectj的环境搭建步骤比较复杂,这里参考了Hujiang的项目,对gradle插件进行了修改封装;
使用步骤:
在项目的build.gradle文件中加入如下代码:
repositories {
...
maven { url 'https://jitpack.io' }
}
dependencies {
...
classpath 'com.github.alfredxl:android-aspectj:0.9.0'
}
在application模块中的build.gradle文件中加入:
apply plugin: 'android-aspectjx'
名称 | 描述 |
---|---|
JPoint | 代码可注入的点,比如一个方法的调用处或者方法内部、“读、写”变量等。 |
Pointcut | 用来描述 JPoint 注入点的一段表达式,比如:调用 Animal 类 fly 方法的地方,call(* Animal.fly(..))。 |
Advice | 常见的有 Before、After、Around 等,表示代码执行前、执行后、替换目标代码,也就是在 Pointcut 何处注入代码。 |
Aspect | Pointcut 和 Advice 合在一起称作 Aspect。 |
符号 | 描述 |
---|---|
* | 表示任何数量的字符,除了(.) |
.. | 表示任何数量的字符包括任何数量的(.) |
+ | 描述指定类型的任何子类或者子接口 |
! | 一 元操作符: |
||、&& | 二 元操作符 |
例子 | 解析 |
---|---|
*Account | 使用Account名称结束的类型,如SavingsAccount和CheckingAccount |
java.*.Date | 类型Date在任何直接的java子包中,如java.util.Date和java.sql.Date |
java..* | 任何在java包或者所有子包中的类型,如java.awt和java.util或者java.awt.event 和java.util.logging |
javax..*Model+ | 所有javax包或者子包中以Model结尾的类型和其所有子类,如TableModel,TreeModel。 |
!vector | 所有除了Vector的类型 |
Vector || Hashtable | Vector或者Hashtable类型 |
java.util.RandomAccess+ | RandomAccess的所有子类 |
void Account.set*(*) | Account中以set开头,并且只有一个参数类型,无返回值的方法 |
void Account.*() | Account中所有的没有参数的void 方法 |
public * Account.*() | Account中所有没有参数的public 方法 |
public * Account.*(..) | Account中所有的public 方法 |
* Account.*(..) | Account中的所有方法,包括private方法 |
!public * Account.*(..) | 所有的非public 方法 |
* Account+.*(..) | 所有的方法,包括子类的方法 |
* java.io.Reader.read(..) | 所有的read方法 |
* java.io.Reader.read(char[],..) | 所有以read(char[])开始的方法,包括read(char[])和read(char[],int,int) |
* javax..*.add*Listener(EventListener+) | 命名以add开始,以Listener结尾的方法,参数中为EventListener或子类 |
* *.*(..) throws RemoteException | 抛出RemoteException的所有方法 |
名称 | 描述 |
---|---|
MethodPattern : | [!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型] |
ConstructorPattern: | [!] [@Annotation] [public,protected,private] [final] [类名.]new(参数类型列表) [throws 异常类型] |
FieldPattern: | [!] [@Annotation] [public,protected,private] [static] [final] 属性类型 [类名.]属性名 |
TypePattern: | 其他 Pattern 涉及到的类型规则也是一样,可以使用 '!'、''、'..'、'+','!' 表示取反,'' 匹配除 . 外的所有字符串,'*' 单独使用事表示匹配任意类型,'..' 匹配任意字符串,'..' 单独使用时表示匹配任意长度任意类型,'+' 匹配其自身及子类,还有一个 '...'表示不定个数 |
语法 | 描述 |
---|---|
execution(MethodPattern) | 方法执行 |
call(MethodPattern) | 方法调用 |
execution(ConstructorPattern) | 构造函数执行 |
call(ConstructorPattern) | 构造函数被调用 |
staticinitialization(TypePattern) | static 块初始化 |
get(FieldPattern) | 属性读操作 |
set(FieldPattern) | 属性写操作 |
handler(TypePattern) | 异常处理 |
adviceexecution() | 所有 Advice 执行 |
语法 | 描述 |
---|---|
within(TypePattern): | TypePattern表示某个包或者类中包含的JPoint,可以使用通配符; |
withincode(ConstructorPattern | MetodPattern): |
cflow(pointcuts): | 比如cflow(call Animal.fly): 标识调用Animal.fly函数时所包含的JPoint,包含fly的call这个JPoint本身; |
cflowbelow(pointcuts): | 比如cflowbelow(call Animal.fly): 标识调用Animal.fly函数时所包含的JPoint,但不包含fly的call这个JPoint本身; |
this(Type): | JPoint所在的对象是否满足instanceof Type条件(注:不能使用通配符,与within类似,但within包含内部类, 而this不包含); |
target(Type): | 与this相对,表示Pointcut所在对象是否满足instanceof Type条件(注:不能使用通配符); |
args(TypeSignature): | Constructor Signature|Method Signature的参数类型,比如args(int,..)表示第一个参数是int, 后面的参数个数和类型不限; |
if(BooleanExpression): | 满足表达式的 Join Point,表达式只能使用静态属性、Pointcuts 或 Advice 暴露的参数、thisJoinPoint 对象。 |
语法 | 描述 |
---|---|
Before(Pointcut): | 在执行JPoint之前; |
After(Pointcut): | 在执行JPonit之后; |
Aroud(Pointcut): | 替换原需要执行的代码,如需执行原代码,需调用joinPoint.proceed()方法,不可与Before和After同时使用; |
AfterReturning(Pointcut): | JPoint 为方法调用且正常 return 时,不指定返回类型时匹配所有类型; |
AfterThrowing(Pointcut): | JPoint 为方法调用且抛出异常时,不指定异常类型时匹配所有类型; |
execution(MethodPattern)(JPoint方法执行)
@Aspect
public class OtherJpoint {
@Before("execution(* android.view.View.OnClickListener.onClick(android.view.View))")
public void onViewClickListener(JoinPoin joinPoint) throws Throwable {
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
Object object = joinPoint.getArgs()[0];
if (object instanceof TextView) {
Log.d("aopLog", "clickViewName: " + ((TextView) object).getText().toString());
}
}
}
}
该切入点执行的效果为:在所有实现View的点击事件的onClick方法内部代码执行之前打印View的信息
execution(ConstructorPattern)(JPoint构造器执行)
@Before("execution(com.aspectj.demo.bean.Person.new(..))")
public void onStartPageConstructor(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "this:" + joinPoint.getThis().toString());
}
该切入点执行的效果为:在所有创建Person对象的时候,执行Person对象构造函数之前打印该对象的信息; 注意: 构造函数执行, 没有返回值类型, 且函数名只能是new
call(MethodPattern) (JPoint方法调用)
@Before("call(* com.aspectj.demo.StartPage.setButtonOneData(..))")
public void onCallStartPagesetButtonOneData(JoinPoint joinPoint) throws Throwable {
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
Object object = joinPoint.getArgs()[0];
Log.d("aopLog", "button_text: " + object.toString());
}
}
该切入点的效果为:在所有调用com.aspectj.demo.StartPage.setButtonOneData(..)该方法的代码之前打印传递给该方法的型参
staticinitialization(TypePattern) (JPoint类初始化)
@Before("staticinitialization(com.aspectj.demo.bean.Person)")
public void onStartPageStaticinitialization(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "staticinitialization_this:" + joinPoint.getStaticPart().toString());
}
该切入点的效果为:在Person初始化之前打印信息
get(FieldPattern) (JPoint属性读操作)
@Before("get(* com.aspectj.demo.bean.Person.*)")
public void getPersonField(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "getPersonField:" + joinPoint.getStaticPart().toString());
}
该切入点的效果为,在所有获取Person对象属性的代码之前插入打印信息
set(FieldPattern) (JPoint属性写操作)
@Before("set(* com.aspectj.demo.bean.Person.*)")
public void setPersonField(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "setPersonField:" + joinPoint.getStaticPart().toString());
}
该切入点的效果为,在所有设置Person对象属性值的代码之前插入打印信息
handler(TypePattern) (JPoint例外处理执行)
private fun showToast(message: String) {
if (message.isNotEmpty()) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
try {
throw IllegalArgumentException()
} catch (e: IllegalArgumentException) {
}
}
@Before("handler(java.lang.IllegalArgumentException)")
public void handlerIllegalArgumentException(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "handlerIllegalArgumentException:" + joinPoint.getStaticPart().toString());
}
该切入点的效果为,在所有编译时匹配的IllegalArgumentException异常,将在异常抛出前,打印信息; 注意: handler只支持Before,而且只能匹配编译期异常捕获,并且不能捕获该异常的子类。
within(TypePattern)
@Pointcut("within(com.aspectj.demo.StartPage)")
public void withinStartPage() {
}
@Pointcut("call(* android.widget.Toast.show(..))")
public void showToast() {
}
@Before("withinStartPage() && showToast()")
public void withinStartPageAndShowToast(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "withinStartPageAndShowToast:" + joinPoint.getStaticPart().toString());
}
该切入点的效果为,在StartPage类中调用Toast方法之前打印相关信息
withincode(MethodPattern)
@Pointcut("withincode(* com.aspectj.demo.StartPage.showToast(..))")
public void inStartPageShowToast() {
}
@Pointcut("call(* android.widget.Toast.show(..))")
public void showToast() {
}
@Before("inStartPageShowToast() && showToast()")
public void inStartPageShowToastAndShowToast(JoinPoint joinPoint) throws Throwable {
Log.d("aopLog", "inStartPageShowToastAndShowToast: " + joinPoint.getSignature().getName());
}
该切入点的效果为:在com.aspectj.demo.StartPage.showToast()方法中调用了Toast方法之前打印相关信息
注解
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface ActivityOnCreate {
}
@Pointcut("execution(@com.aspectj.aspectjaplay.activitylife.ActivityOnCreate * *(..))")
public void ActivityOnCreate() {
}
@Around("ActivityOnCreate()")
public void ActivityOnCreate(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long endTime = System.currentTimeMillis();
String key = joinPoint.getSignature().getName();
Log.d("aopLog", "execution_onActivityMethodAround: " + joinPoint.getThis().getClass().getSimpleName() + "_"
+ key + ":" + (endTime - startTime));
}
该切入点的效果为,在使用了ActivityOnCreate 注解的目标方法执行前进行拦截并实行相关操作
这里关于Around的切入方式需要特殊说明一下,由于Around是切入整个方法的执行体,把整个方法执行体作为一个返回参数,所以在切有返回值的方法时,必须如下定义
@Around("Test()")
public Object Test(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
return object ;
}
即切入点方法撰写的时候必须是返回Object的方法,然后返回 joinPoint.proceed() 的值
本篇介绍,对AOP及Aspectj的介绍仅仅只涉及到到入门知识,更深入的学习,欢迎大家一起加入@QQ32038305。