Android AOP 之 Aspectj 实践

1、AOP的概念:

        面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为侧面(aspect,又译作方面)的语言构造为基础,侧面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。
侧面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与侧面相关的编程概念还包括元对象协议、主题(subject)、混入(mixin)和委托。(摘自维基百科)

2、AOP在Android中的应用点:

      aop是一种面向切面的编程思想,目的在于从切面插入自己需要的代码,在不破坏、不耦合原有代码逻辑的基础上,实现相关的功能,在Android中的应用场景非常丰富,如:数据统计、日志记录、用户行为统计、应用性能统计、数据校验、行为拦截,等等应用场景。

3、在Android中,如何实现AOP编程:

      aop编程的实现技术方案有很以下几种:

      1、APT(直接插入java文件);

      2、Aspectj(编织class文件);

      3、javassist(修改class文件);

     通这里主要介绍aspectj的使用

4、Aspectj框架入门介绍:

  1. 环境搭建:

    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'

    aspectj介绍

    aspectj术语

    名称

    描述

    JPoint代码可注入的点,比如一个方法的调用处或者方法内部、“读、写”变量等。
    Pointcut用来描述 JPoint 注入点的一段表达式,比如:调用 Animal 类 fly 方法的地方,call(* Animal.fly(..))。
    Advice常见的有 Before、After、Around 等,表示代码执行前、执行后、替换目标代码,也就是在 Pointcut 何处注入代码。
    AspectPointcut 和 Advice 合在一起称作 Aspect。

    JPoint语法:

    符号

    描述

    *表示任何数量的字符,除了(.)
    ..表示任何数量的字符包括任何数量的(.)
    +描述指定类型的任何子类或者子接口
    !一 元操作符:
    ||、&& 二 元操作符

     

    主要例子:

    例子

    解析

    *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 || HashtableVector或者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的所有方法

     

    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 涉及到的类型规则也是一样,可以使用 '!'、''、'..'、'+','!' 表示取反,'' 匹配除 . 外的所有字符串,

    '*' 单独使用事表示匹配任意类型,'..' 匹配任意字符串,'..' 单独使用时表示匹配任意长度任意类型,'+' 匹配其自身及子类,

    还有一个 '...'表示不定个数

    Pointcut语法:

    语法

    描述

    execution(MethodPattern)方法执行
    call(MethodPattern)方法调用
    execution(ConstructorPattern)构造函数执行
    call(ConstructorPattern)构造函数被调用
    staticinitialization(TypePattern)static 块初始化
    get(FieldPattern)属性读操作
    set(FieldPattern)属性写操作
    handler(TypePattern)异常处理
    adviceexecution()所有 Advice 执行

    间接选择Pointcut语法:

    语法

    描述

    within(TypePattern):  TypePattern表示某个包或者类中包含的JPoint,可以使用通配符;
    withincode(ConstructorPattern|MetodPattern): 表示某个类的构造函数或方法中涉及到的JPoint;
    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 对象。

     

    Advice语法:

    语法

    描述

    Before(Pointcut): 在执行JPoint之前;
    After(Pointcut):在执行JPonit之后;
    Aroud(Pointcut):替换原需要执行的代码,如需执行原代码,需调用joinPoint.proceed()方法,不可与Before和After同时使用
    AfterReturning(Pointcut):JPoint 为方法调用且正常 return 时,不指定返回类型时匹配所有类型
    AfterThrowing(Pointcut):JPoint 为方法调用且抛出异常时,不指定异常类型时匹配所有类型

     

    aspectj示例:

    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的介绍仅仅只涉及到到入门知识,更输入的学习,欢迎大家一起加入。

Github项目地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值