阅读 452

Android AOP 之AspectJ

前言

这篇文章说的是AOP编程方式的 AspectJ 。需要了解注解相关知识juejin.cn/post/692232…

www.iteye.com/blog/jinnia…

简介

AspectJ 是在静态织入代码,即在编译期注入代码的。AspectJ是一种严格意义上的AOP技术,因为它提供了完整的面向切面编程的注解,这样让使用者可以在不关心字节码原理的情况下完成代码的织入,因为编写的切面代码就是要织入的实际代码。AspectJ 以切面(aspect)为基础,切面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关点(crosscutting concern)。

概念

  • Aspect :切面,一个关注点的模块化,这个关注点可能会横切多个对象。
  • Join Point :连接点,程序中可切入的点,例如方法调用时、读取某个变量时。
  • Pointcut :切入点,代码注入的位置,其实就是有条件限定的 Join Point,例如只在特定方法中注入代码。
  • Advice :通知,在切入点注入的代码,一般有 before、after、around 三种类型。
  • Target Object :被一个或多个 aspect 横切拦截操作的目标对象。
  • Weaving : 把 Advice 代码织入到目标对象的过程。
  • Inter-type declarations : 用来给一个类型声明额外的方法或属性。

使用场景

AspectJ实际常见的场景大致包括

  • 统计埋点
  • 日志打印/打点
  • 数据校验
  • 行为拦截
  • 性能监控
  • 动态权限控制

使用方式

  • 使用 AspectJ 的语言进行开发
  • 通过 AspectJ 提供的注解在 Java 语言上开发

Android Studio 上一般使用注解的方式使用 AspectJ,因为 Android Studio 没有 AspectJ 插件,无法识别 AspectJ 的语法,所以使用 注解的方式使用

基本语法

Join Point

Join Point 表示连接点,即 AOP 可织入代码的点

Join Point说明
Method call方法被调用
Method execution方法执行
Constructor call构造函数被调用
Constructor execution构造函数执行
Field get读取属性
Field set写入属性
Static initializationstatic 块初始化
Handler异常处理
Advice execution所有 Advice 执行

Pointcuts

Pointcuts 是具体的切入点,可以确定具体织入代码的地方

Join PointPointcuts syntax
Method callcall(MethodPattern)
Method executionexecution(MethodPattern)
Constructor callcall(ConstructorPattern)
Constructor executionexecution(ConstructorPattern)
Field getget(FieldPattern)
Field setset(FieldPattern)
Pre-initializationinitialization(ConstructorPattern)
Initializationpreinitialization(ConstructorPattern)
Static initializationstaticinitialization(TypePattern)
Handlerhandler(TypePattern)
Advice executionadviceexcution()

上面与 Join Point 对应的选择外,Pointcuts 还有其他选择方法:

Pointcuts synatx说明
within(TypePattern)符合 TypePattern 的代码中的 Join Point
withincode(MethodPattern)在某些方法中的 Join Point
withincode(ConstructorPattern)在某些构造函数中的 Join Point
cflow(Pointcut)Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,包括 P 本身
cflowbelow(Pointcut)Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,不包括 P 本身
this(Type or Id)Join Point 所属的 this 对象是否 instanceOf Type 或者 Id 的类型
target(Type or Id) Join Point 所在的对象(例如 call 或 execution 操作符应用的对象)是否 instanceOf Type 或者 Id 的类型
args(Type or Id, ...)方法或构造函数参数的类型
if(BooleanExpression)满足表达式的 Join Point,表达式只能使用静态属性、Pointcuts 或 Advice 暴露的参数、thisJoinPoint 对象

Pointcut 表达式还可以 !、&&、|| 来组合,!Pointcut 选取不符合 Pointcut 的 Join Point,Pointcut0 && Pointcut1 选取符合 Pointcut0 和 Pointcut1 的 Join Point,Pointcut0 || Pointcut1 选取符合 Pointcut0 或 Pointcut1 的 Join Point。

Pointcuts 的语法中涉及到一些 Pattern,下面是这些 Pattern 的规则

Pattern规则
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 涉及到的类型规则也是一样,可以使用 '!'、''、'..'、'+','!' 表示取反,'' 匹配除 . 外的所有字符串,'*' 单独使用事表示匹配任意类型,'..' 匹配任意字符串,'..' 单独使用时表示匹配任意长度任意类型,'+' 匹配其自身及子类,还有一个 '...'表示不定个数

语法官网 www.eclipse.org/aspectj/doc…

常用的Pointcut 例子

  • execution(void void android.view.View.OnClickListener+.onClick(..)) -- OnClickListener 及其子类的 onClick 方法执行时

  • call(@retrofit2.http.GET public * com.johnny.core.http..*(..)) -- 'com.johnny.core.http'开头的包下面的所有 GET 方法调用时

  • call(android.support.v4.app.Fragment+.new(..)) -- support 包中的 Fragment 及其子类的构造函数调用时

  • set(@Inject * *) -- 写入所有 @Inject 注解修饰的属性时

  • handler(IOException) && within(com.johnny.core.http..) -- 'com.johnny.core.http'开头的包代码中处理 IOException 时

  • execution(void setUserVisibleHint(..)) && target(android.support.v4.app.Fragment) && args(boolean) -- 执行 Fragment 及其子类的 setUserVisibleHint(boolean) 方法时

  • execution(void Foo.foo(..)) && cflowbelow(execution(void Foo.foo(..))) -- 执行 Foo.foo() 方法中再递归执行 Foo.foo() 时

Pointcut 声明

Pointcuts 可以在普通的 class 或 Aspect class 中定义,由 org.aspectj.lang.annotation.Pointcut 注解修饰的方法声明,方法返回值只能是 void。@Pointcut 修饰的方法只能由空的方法实现而且不能有 throws 语句,方法的参数和 pointcut 中的参数相对应。

@Aspect
class Test {
    @Pointcut("execution(void Foo.foo(..)")
    public void executFoo() {}

    @Pointcut("executFoo() && cflowbelow(executFoo()) && target(foo) && args(i)")
    public void loopExecutFoo(Foo foo, int i) {}
}
复制代码

Advice

Advice 是在切入点上织入的代码,在 AspectJ 中有五种类型:Before、After、AfterReturning、AfterThrowing、Around。

Advice说明
@Before在执行 Join Point 之前
@After在执行 Join Point 之后,包括正常的 return 和 throw 异常
@AfterReturningJoin Point 为方法调用且正常 return 时,不指定返回类型时匹配所有类型
@AfterThrowingJoin Point 为方法调用且抛出异常时,不指定异常类型时匹配所有类型
@Around替代 Join Point 的代码,如果要执行原来代码的话,要使用 ProceedingJoinPoint.proceed()

fter 和 Before 没有返回值,但是 Around 的目标是替代原 Join Point 的,所以它一般会有返回值,而且返回值的类型需要匹配被选中的 Join Point 的代码。而且不能和其他 Advice 一起使用,如果在对一个 Pointcut 声明 Around 之后还声明 Before 或者 After 则会失效。

Advice 注解修改的方法必须为 public,Before、After、AfterReturning、AfterThrowing 四种类型修饰的方法返回值也必须为 void,Advice 需要使用 JoinPoint、JoinPointStaticPart、JoinPoint.EnclosingStaticPart 时,要在方法中声明为额外的参数,@Around 方法可以使用 ProceedingJoinPoint,用以调用 proceed() 方法。

Aspect

Aspect 就是 AOP 中的关键单位 -- 切面,我们一般会把相关 Pointcut 和 Advice 放在一个 Aspect 类中,在基于 AspectJ 注解开发方式中只需要在类的头部加上 @Aspect 注解即可,@Aspect 不能修饰接口。 具体使用可以试试 ,自己照着撸一套,juejin.cn/post/684490…

文章分类
Android
文章标签