AOP之AspectJ在Android中的使用

图1

什么是AOP?

AOP:(Aspect Oriented Programming)面向切面编程,通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术。也就是在不改变现有代码业务逻辑的基础上,通过注解或者动态代理的方式来将新的逻辑代码通过预编译的方式添加到原来的业务代码中。而在代码层面上原有的业务逻辑是不发生变化的。

什么是AspectJ?

AspectJ:是一个面向切面的框架,它扩展了java语言。AspectJ定义了AOP的语法,它有一个专门的编译器用来生成遵守java字节码规范的Class文件。

使用AOP语法,我们能够很容易的写出AOP相关的代码;同时,AspectJ支持android.当然这种支持并不是官方的,只是网上有大牛基于gradle编写了一些脚本,让其可以运行在gradle管理的android项目中。github上也有相应的开源库可以直接引用,相当的方便。

AspectJ的术语

  1. joinPoint: 执行点,一个类中并不是所有的地方都是可以被当作切面来插入代码的,那些可以被当作切面插入代码的地方,就称之为执行点。比如:调用方法前,调用方法后,或者方法执行前,方法执行后以及获取某个变量或者设置某个变量都是可以称为程序的执行点的。
  2. pointcut: 切入点,是一个基于正则表达式的表达式,其本身是一个表达式,只是通过这个表达式,可以选取程序中我们感兴趣的执行点或者程序执行点的集合。
  3. advice:在通过pointcut选取出来的执行点上要执行的操作,逻辑。advice只是一个泛称,它具体所指的主要是BeforeAfterAfterReturningAfterThrowingAround等5类通知对象。

Advice通知类型的说明

  1. Before: 在方法执行之前执行要插入的代码
  2. After: 在方法执行之后执行要插入的代码
  3. AfterReturning: 在方法执行后,返回一个结果再执行,如果没结果,用此修辞符修辞是不会执行的。

    @AfterReturning(value = "debugTraceMethod()", returning = "result")
        public void callMethod(JoinPoint joinPoint, Object result) {
            Log.d("tag", joinPoint.getSignature() + "-------" + joinPoint.getTarget() + "-------returnValue:" + result);
        }
    
  4. AfterThrowing: 在方法执行过程中抛出异常后执行,也就是方法执行过程中,如果抛出异常后,才会执行此切面方法。

    @AfterThrowing(value = "debugTraceMethod()")
    public void callMethod(JoinPoint joinPoint) {
        Log.d("tag", joinPoint.getSignature() + "-------" + joinPoint.getTarget());
    }
    
  5. Around: 在方法执行前后和抛出异常时执行(前面几种通知的综合)

call和execution的说明

call是在方法被调用前后执行切面的代码,而execution是在方法执行前后执行切面的代码。一个是在方法外执行,一个是在方法内部执行。

Pointcut切点语法

@Pointcut("execution(int com.winbons.sanbot.testaspectj.MainActivity.handleClick(..))")
public void callMethod() {

}

上面的代码中,分别有下面意思:

@pointcut:代表选取执行点的规则表达式
execution:表示切面代码会在方法执行前后被调用
int:表示方法的返回值类型  一般使用``*``表示任意返回类型
com.winbons.sanbot.testaspectj.MainActivity:指定执行点的位置,可以使用正则进行匹配
.handleClick(..):表示需要执行切面代码的方法名,``(..)``表示参数,这里的点表示任意参数

AspectJ简单示例

创建一个简单的AspectJ需要做两件事情。

  1. 编写pointcut表达式来选取执行点。
  2. 编写需要插入执行点的切面代码。

1.编写pointcut表达式选取执行点

@Pointcut("execution(* com.allen.testaspectj.MainActivity.handleClick(..))")
public void methodAspect() {

}

这里选取的是MainActivity中的handleClick带任意参数的方法。

private void handleClick() {
    Log.d("tag", "onclick");
}

2. 编写需要插入执行点的切面代码

@Before("methodAspect()")
public void callMethod(JoinPoint joinPoint) {
    Log.d("tag", joinPoint.getTarget() + "-----" + joinPoint.getSignature());
}

这里使用的Advice类型为Before,也就是在方法执行前先执行切面的代码。这里的打印结果为:

05-31 11:25:30.065 2564-2564/com.allen.testaspectj D/tag: com.allen.testaspectj.MainActivity@21ef237b-----void com.allen.testaspectj.MainActivity.handleClick()
05-31 11:25:30.065 2564-2564/com.allen.testaspectj D/tag: onclick

3. 精简综合

上面的两个步骤,实际上可以写成下面的一步即可完成执行点的选取和切面代码的编写。

@Before("execution(* com.allen.testaspectj.MainActivity.handleClick(..))")
public void callMethod(JoinPoint joinPoint) {
    Log.d("tag", joinPoint.getTarget() + "-----" + joinPoint.getSignature());
}

去看@pointcut,而直接将表达式写在了Advice中的方式也是可行的,而且代码更加的精简。

自定义PointCut

除了直接指定路径来选取相应的方法来作为执行点,我们还可以通过自定义的方式来通过写自定义注解的方式来标注JoinPoint。具体的做法分为三步:

  1. 写自定义注解
  2. 写切面代码及选取执行点
  3. 在需要植入切面代码的地方使用注解标注

1. 写自定义注解

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface DebugMethod {
}

这里主要定义了注解被保留在编译时和此注解的使用目标为构造方法和普通方法。

2. 写切面代码及选取执行点

@Aspect
public class CustomAspect {
    @Pointcut("execution(@com.allen.testaspectj.DebugMethod * *(..))")
    public void methodAspect() {

    }

    @Before("methodAspect()")
    public void aspect(JoinPoint joinPoint) {
        Log.d("tag", joinPoint.getSignature() + "----" + joinPoint.getTarget());
    }
}

这里做了两个操作,一个是选取了执行点,这里和简单示例中表达式的不同之处在于:

  1. 自定义的pointcut不选取具体的方法,而是直接选择注解类。@com.allen.testaspectj.DebugMethod
  2. * *(..)第一个*代表的是方法的返回值为任意类型 第二个*代表方法名为任意类型 (..)代表参数为任意参数即0~n个参数.

另一个操作也就是写了相应的切面代码。

使用注解进行标注

@DebugMethod
private void handleClick() {
    Log.d("tag", "onclick");
}

这里直接使用注解标注到需要插入切面代码的方法上即可完成标注。

运行的结果为:

05-31 11:49:58.169 24871-24871/com.allen.testaspectj D/tag: void com.allen.testaspectj.MainActivity.handleClick()----com.allen.testaspectj.MainActivity@21ef237b
05-31 11:49:58.169 24871-24871/com.allen.testaspectj D/tag: onclick

AOP应用

  1. 日志
  2. 性能监控
  3. 埋点
  4. 持久化
  5. 数据校验
  6. 缓存

到这里,AspectJ的一般用法已经讲完了。