android面向切面AOP编程精华总结

AspectJ语法

1.Join Points

JPoints就是程序运行时的一些执行点。
这里写图片描述
最经常使用的是call和execution的执行点。

2.Pointcuts

怎么从一堆一堆的JPoints中选择自己想要的JPoints呢?恩,这就是Pointcuts的功能。一句话,Pointcuts的目标是提供一种方法使得开发者能够选择自己感兴趣的JoinPoints。

一个Pointcuts例子:
public pointcut  testAll(): call(public  *  *.println(..)) && !within(TestAspect) ;

第一个public:表示这个pointcut是public访问。这主要和aspect的继承关系有关,属于AspectJ的高级玩法,本文不考虑。

 pointcut:关键词,表示这里定义的是一个pointcut。pointcut定义有点像函数定义。总之,在AspectJ中,你得定义一个pointcut。

Ž testAll():pointcut的名字。在AspectJ中,定义Pointcut可分为有名和匿名两种办法。个人建议使用named方法。因为在后面,我们要使用一个pointcut的话,就可以直接使用它的名字就好。

 testAll后面有一个冒号,这是pointcut定义名字后,必须加上。冒号后面是这个pointcut怎么选择Joinpoint的条件。

call(public * *.println(..)):是一种选择条件。call表示我们选择的Joinpoint类型为call类型。
‘ public **.println(..):这小行代码使用了通配符。由于我们这里选择的JoinPoint类型为call类型,它对应的目标JPoint一定是某个函数。所以我们要找到这个/些函数。public 表示目标JPoint的访问类型

(public/private/protect)。第一个表示返回值的类型是任意类型。第二个用来指明包名。此处不限定包名。紧接其后的println是函数名。这表明我们选择的函数是任何包中定义的名字叫println的函数。当然,唯一确定一个函数除了包名外,还有它的参数。在(..)中,就指明了目标函数的参数应该是什么样子的。比如这里使用了通配符..,代表任意个数的参数,任意类型的参数。

’call后面的&&:AspectJ可以把几个条件组合起来,目前支持 &&,||,以及!这三个条件。这三个条件的意思不用我说了吧?和Java中的是一样的。

“!within(TestAspectJ):前面的!表示不满足某个条件。within是另外一种类型选择方法,特别注意,这种类型和前面讲到的joinpoint的那几种类型不同。within的类型是数据类型,而joinpoint的类型更像是动态的,执行时的类型。
上例中的pointcut合起来就是:
l 选择那些调用println(而且不考虑println函数的参数是什么)的Joinpoint。
l 另外,调用者的类型不要是TestAspect的。

3.Jpoints和pointcut之间的联系

这里写图片描述

①直接针对JoinPoint的选择:

一个Method Signature的完整表达式为:

@注解 访问权限 返回值的类型 包名.函数名(参数)
Œ @注解和访问权限(public/private/protect,以及static/final)属于可选项。如果不设置它们,则默认都会选择。以访问权限为例,如果没有设置访问权限作为条件,那么public,private,protect及static、final的函数都会进行搜索。
 返回值类型就是普通的函数的返回值类型。如果不限定类型的话,就用*通配符表示
Ž 包名.函数名用于查找匹配的函数。可以使用通配符,包括和..以及+号。其中号用于匹配除.号之外的任意字符,而..则表示任意子package,+号表示子类。
比如:
java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date
Test*:可以表示TestBase,也可以表示TestDervied
java..*:表示java任意子类
java..*Model+:表示Java任意package中名字以Model结尾的子类,比如TabelModel,TreeModel

 最后来看函数的参数。参数匹配比较简单,主要是参数类型,比如:
(int, char):表示参数只有两个,并且第一个参数类型是int,第二个参数类型是char
(String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限。在参数匹配中,
..代表任意参数个数和类型
(Object …):表示不定个数的参数,且类型都是Object,这里的…不是通配符,而是Java中代表不定参数的意思

一个Constructorsignature的完整表达式为:

Constructorsignature和Method Signature类似,只不过构造函数没有返回值,而且函数名必须叫new。比如:
public *..TestDerived.new(..):
Œ public:选择public访问权限
 *..代表任意包名
Ž TestDerived.new:代表TestDerived的构造函数
 (..):代表参数个数和类型都是任意
再来看Field Signature和Type Signature
Field Signature标准格式:
@注解 访问权限 类型 类名.成员变量名
Œ 其中,@注解和访问权限是可选的
 类型:成员变量类型,*代表任意类型
Ž 类名.成员变量名:成员变量名可以是*,代表任意成员变量
比如,
set(inttest..TestBase.base):表示设置TestBase.base变量时的JPoint
Type Signature:直接上例子
staticinitialization(test..TestBase):表示TestBase类的static block
handler(NullPointerException):表示catch到NullPointerException的JPoint。注意,图2的源码第23行截获的其实是Exception,其真实类型是NullPointerException。但是由于JPointer的查询匹配是静态的,即编译过程中进行的匹配,所以handler(NullPointerException)在运行时并不能真正被截获。只有改成handler(Exception),或者把源码第23行改成NullPointerException才行。

②间接针对JoinPoint的选择:

这里写图片描述

注意:this()和target()匹配的时候不能使用通配符。不是所有的AOP实现都支持本节所说的查询条件。比如Spring就不支持withincode查询条件。

4.advice和aspect

①advice

简单点说,advice就是一种Hook。
ASpectJ中有好几个Hook,主要是根据JPoint执行时机的不同而不同,比如下面的:
这里写图片描述

注意,after和before没有返回值,但是around的目标是替代原JPoint的,所以它一般会有返回值,而且返回值的类型需要匹配被选中的JPoint。

(1)往advice传递参数:
作用:比方所around advice,我可以对传入的参数进行检查,如果参数不合法,我就直接返回,根本就不需要调用proceed做处理。
往advice传参数比较简单,就是利用前面提到的this(),target(),args()等方法。具体方法如下:
Œ 先在pointcuts定义时候指定参数类型和参数名:

pointcut testAll(Test.TestDerived derived,int x):call(*Test.TestDerived.testMethod(..))
             && target(derived)&& args(x)

此处的target和args括号中用得是参数名。而参数名则是在前面pointcuts中定义好的。
或者:

pointcut testAll():call(*Test.TestDerived.testMethod(..)) && target(Test.TestDerived) &&args(int)

(2)advice接收参数改造
advice的定义现在也和函数定义一样,把参数类型和参数名传进来。

Object around(Test.TestDerived derived,int x):testAll(derived,x){
     System.out.println("     arg1=" + derived);
     System.out.println("     arg2=" + x);
      return proceed(derived,x); //注意,proceed就必须把所有参数传进去。
}

这里写图片描述

②aspect

上面这些东西都有点像函数定义,在Java中,这些东西都是要放到一个class里的。在AspectJ中,也有类似的数据结构,叫aspect。
aspect的定义:

public aspect 名字 {//aspect关键字和class的功能一样,文件名以.aj结尾
 pointcuts定义...
 advice定义...
}

你看,通过这种方式,定义一个aspect类,就把相关的JPoint和advice包含起来,是不是形成了一个“关注面”?比如:

l 我们定义一个LogAspect,在LogAspect中,我们在关键JPoint上设置advice,这些advice就是打印日志
l 再定义一个SecurityCheckAspect,在这个Aspect中,我们在关键JPoint上设置advice,这些advice将检查调用app是否有权限。

通过这种方式,我们在原来的JPoint中,就不需要写log打印的代码,也不需要写权限检查的代码了。所有这些关注点都挪到对应的Aspectj文件中来控制。恩,这就是AOP的精髓。

综合前面4点,可以得知,总体的流程是这样的:
这里写图片描述

5.使用aop的例子

1.打印Log

package com.androidaop.demo;
import android.util.Log;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;

@Aspect   //必须使用@AspectJ标注,这样class DemoAspect就等同于 aspect DemoAspect了
public class DemoAspect {
    staticfinal String TAG = "DemoAspect";
/*
@Pointcut:pointcut也变成了一个注解,这个注解是针对一个函数的,比如此处的logForActivity()
其实它代表了这个pointcut的名字。如果是带参数的pointcut,则把参数类型和名字放到
代表pointcut名字的logForActivity中,然后在@Pointcut注解中使用参数名。
基本和以前一样,只是写起来比较奇特一点。后面我们会介绍带参数的例子
*/
@Pointcut("execution(* com.androidaop.demo.AopDemoActivity.onCreate(..)) ||"
        +"execution(* com.androidaop.demo.AopDemoActivity.onStart(..))")
public void logForActivity(){};  //注意,这个函数必须要有实现,否则Java编译器会报错

/*
@Before:这就是Before的advice,对于after,after -returning,和after-throwing。对于的注解格式为
@After,@AfterReturning,@AfterThrowing。Before后面跟的是pointcut名字,然后其代码块由一个函数来实现。比如此处的log。
*/
    @Before("logForActivity()")
    public void log(JoinPoint joinPoint){
       //对于使用Annotation的AspectJ而言,JoinPoint就不能直接在代码里得到了,而需要通过
      //参数传递进来。
       Log.e(TAG, joinPoint.toShortString());
    }
}

提示:如果开发者已经切到AndroidStudio的话,AspectJ注解是可以被识别并能自动补齐。

上面的例子仅仅是列出了onCreate和onStart两个函数的日志,如果想在所有的onXXX这样的函数里加上log,该怎么改呢?

@Pointcut("execution(* *..AopDemoActivity.on*(..))")  
public void logForActivity(){};  
2.检查权限

(1)自定义注解
如果我有10个API,10个不同的权限,那么在10个函数的注释里都要写,太麻烦了。怎么办?这个时候我想到了注解。注解的本质是源代码的描述。权限声明,从语义上来说,其实是属于API定义的一部分,二者是一个统一体,而不是分离的。
Java提供了一些默认的注解,不过此处我们要使用自己定义的注解:

package com.androidaop.demo;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  

//第一个@Target表示这个注解只能给函数使用  
//第二个@Retention表示注解内容需要包含的Class字节码里,属于运行时需要的。  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface SecurityCheckAnnotation {//@interface用于定义一个注解。  
    public String declaredPermission();  //declarePermssion是一个函数,其实代表了注解里的参数  
}  
怎么使用注解呢?接着看代码:  
//为checkPhoneState使用SecurityCheckAnnotation注解,并指明调用该函数的人需要声明的权限  
   @SecurityCheckAnnotation(declaredPermission="android.permission.READ_PHONE_STATE")  
   private void checkPhoneState(){  
        //如果不使用AOP,就得自己来检查权限  
       if(checkPermission("android.permission.READ_PHONE_STATE") ==false){  
           Log.e(TAG,"have no permission to read phone state");  
           return;  
        }  
       Log.e(TAG,"Read Phone State succeed");  
       return;  
    }  

(2)检查权限


/*
来看这个Pointcut,首先,它在选择Jpoint的时候,把@SecurityCheckAnnotation使用上了,这表明所有那些public的,并且携带有这个注解的API都是目标JPoint
接着,由于我们希望在函数中获取注解的信息,所有这里的poincut函数有一个参数,参数类型是
SecurityCheckAnnotation,参数名为ann
这个参数我们需要在后面的advice里用上,所以pointcut还使用了@annotation(ann)这种方法来告诉
AspectJ,这个ann是一个注解
*/
  @Pointcut("execution(@SecurityCheckAnnotation public * *..*.*(..)) && @annotation(ann)")
  publicvoid checkPermssion(SecurityCheckAnnotationann){};

/*
接下来是advice,advice的真正功能由check函数来实现,这个check函数第二个参数就是我们想要
的注解。在实际运行过程中,AspectJ会把这个信息从JPoint中提出出来并传递给check函数。
*/
   @Before("checkPermssion(securityCheckAnnotation)")
    publicvoid check(JoinPoint joinPoint,SecurityCheckAnnotationsecurityCheckAnnotation){
        //从注解信息中获取声明的权限。
       String neededPermission = securityCheckAnnotation.declaredPermission();
       Log.e(TAG, joinPoint.toShortString());
       Log.e(TAG, "\tneeded permission is " + neededPermission);
       return;
    }

这里写图片描述
恩,这其实是Aspect的真正作用,它负责收集Jpoint,设置advice。一些简单的功能可在Aspect中来完成,而一些复杂的功能,则只是有Aspect来统一收集信息,并交给专业模块来处理。
最终代码:

@Before("checkPermssion(securityCheckAnnotation)")  
  publicvoid check(JoinPoint joinPoint,SecurityCheckAnnotation securityCheckAnnotation){  
     String neededPermission = securityCheckAnnotation.declaredPermission();  
     Log.e(TAG, "\tneeded permission is " + neededPermission);  
      SecurityCheckManager manager =SecurityCheckManager.getInstanc();  
     if(manager.checkPermission(neededPermission) == false){  
         throw new SecurityException("Need to declare permission:" + neededPermission);  
      }  
     return;  
  }  

6.AspectJ在Android studio中的配置

在Android里边,我们用得是第二种方法,即对class文件进行处理。
配置总结:
①在原工程下新建一个android library类型的moudle,然后添加AspectJ依赖至module中
compile 'org.aspectj:aspectjrt:1.8.9'
②编写moudle的gradle脚本
A。这几个配置要和原有的app下的配置一致:

 compile 'com.android.support:appcompat-v7:22.2.1'
 compileSdkVersion 22
 buildToolsVersion '23.0.1'

B。在build的过程中,会下载jar包,要开启vpn进行下载,下载过程没有进度条提示,只有一个没有标示进度的滚动横条,如果顺利的话会在10分钟之内下载build好这个moudle,接下的app的gradle就好办了。
③编写app的gradle脚本
A。特别要注意这个,里面的名字要和你在①建立的moudle的名字一致

compile project(':aspectjlibrary')

B。这几个配置还是要和原有的app的gradle保持一致:

 compileSdkVersion 21
 buildToolsVersion '22.0.1'
 minSdkVersion 15
 targetSdkVersion 21

然后直接build app下的这个gradle就可以了,这个就很快了,20秒之内就可以build完成了。


参考文献:
Android 基于AOP监控之——AspectJ构建指南

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值