Android aop切点表达式(execution)

前言

本文是AOP入门系列的基础文章,不会讲太多很深入的东西,如果为你想学习AOP,但是遇到了种种障碍而无法进一步学习,那本文或许能给你提供一些帮助和思路。

我个人在学习aop的过程中也和很多新手一样碰到过各种麻烦的问题,比如说在入门aop的时候,需要做aspectj相关的配置,这一步失败了就无法继续coding;比如,切点表达式写错了,那就无法拦截原生方法做下一步的业务逻辑的操作;甚至说对aop相关的很多概念模糊不清,只知道几个常用的相关注解@Aspect@Before@Around,只能看着别人的demo去敲一下常用的案例却无法在自己的项目中灵活的定制自己的aop。不过没有关系,这个系列,我会从基础---应用---原理帮大家了解aop相关的重要知识。

推荐阅读系列文章

Android aop Advice(通知、增强)
Android aop(AspectJ)查看新的代理类
Android AOP面向切面编程详解
防止按钮连续点击

下面开始讲解aop切点表达式,在这之前我希望你做好2件事:

  • 1、aop是做什么的?能解决什么问题?为什么要用它?
  • 2、android studio中配置好aop。推荐使用aspectjx

一、案例分析:

@Aspect
public class Test {
    final String TAG = Test.class.getSimpleName();

    @Before("execution(* *..MainActivity.on*(..))")
    public void logLifeCycle(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getThis().getClass().getSimpleName();
        Log.e(TAG, "class:" + className+"   method:" + methodSignature.getName());
    }

    @Around("execution(* *..MainActivity.testAOP())")
    public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String key = proceedingJoinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodAroundFirst: before " + key+"do something");
        //执行原方法
        proceedingJoinPoint.proceed();
        Log.e(TAG, "onActivityMethodAroundSecond: after " + key+"do something");
    }
}

这里写了2个方法,logLifeCycle()是在MainActivity的生命周期的方法中打印log日志;onActivityMethodAround是拦截MainActivity中的一个testAOP()方法,在原方法执行执行前、后做一些事情。

重点是搞清楚@Aspect@Before@Around这几个注解,难点是切点表达式的书写。"execution(* *..MainActivity.on*(..))"execution(* *..MainActivity.testAOP())就是切点表达式

这个地方是非常容易犯错的,因为很多人对这个规则没有搞懂,对很多概念模糊不清,所以很多新手难以自定义切点表达式。

下面从几个基础概念出发,带你逐步掌握自定义切点表达式。
(提示:别看见概念有点多,分类也多就害怕了,其实不用害怕的,这里先做一个全方位的了解,后面写表达式的时候是很简单的)

二、AOP相关的几个基础概念

  • Joinpoint(连接点):是指那些被拦截到的点,比如说方法的调用,成员属性的访问甚至异常的处理;
  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
  • Advice(通知):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程. AspectJ 采用编译期织入和类装在期织入 。
  • Aspect(切面):是切入点和通知(引介)的结合 。

2.1Advice(通知)

类型 描述
Before 前置通知, 在目标执行之前执行通知
After 后置通知, 目标执行后执行通知
Around 环绕通知, 在目标执行中执行通知, 控制目标执行时机
AfterReturning 后置返回通知, 目标返回时执行通知
AfterThrowing 异常通知, 目标抛出异常时执行通知

2.2切入点指示符

切入点指示符用来指示切入点表达式目的,AspectJ切入点指示符如下:

  • execution:用于匹配方法执行的连接点

  • within:限制链接点匹配指定的类型

  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配

  • target:限制链接点匹配目标对象为指定类型的类

  • @within:匹配所有使用了xx注解的类(注意是类)

  • @annotation: 匹配使用了xx注解的方法(注意是方法)

2.3切入点表达式语法

比如上面案例中的execution表达式

  execution(*  *..MainActivity.on*(..))
  • execution():切入点指示符;
  • 第一个*:表示任意方法返回类型;
  • *..:表示省略了MainActivity的包名,当然这里也可以写出完整包名;
  • on*表示MainActivity中所有以on开头的方法;
  • (..):表示方法的参数可以是任意类型且个数任意

AspectJ类型匹配的通配符:

  1、 *:匹配任何数量字符;

  2、..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。

  3、+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。

三、@Pointcut

3.1、@Pointcut的基本定义

这里还是以打印Activity生命周期为例,先看看之前的写法

    @Before("execution(* *..MainActivity.on*(..))")
    public void logLifeCycle(JoinPoint joinPoint)  { }

这里直接用通知@Before+切入点表达式就完成了。

下面看看另一种实现方式
定义切点

  //切点表达式
  @Pointcut("execution(* *..MainActivity.on*(..))")   
  //切点签名方法
  private void log(){}

使用定义的Pointcut

    //使用切点方法  
    @Before("log()")
    public void logLifeCycle(JoinPoint joinPoint)  { }

这2种方式的实现效果是完全一样的,但是方法2比方法1更灵活。@Pointcut是专门用来定义切点的,它让切点表达式可以复用。

3.2、@Pointcut带参数

3.2切点指示符

切点指示符是切点定义的关键字,切点表达式以切点指示符开始。可以切点指示符来告诉切点将要匹配什么,有以下9种切点指示符:executionwithinthistarget@within@annotation,下面一一介结这几种切点指示符。

提示:由于篇幅有限,下面只展示切点表达式,完整代码可以在文末地址下载查看

execution

execution表达式语法:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

注意:execution的粒度为方法,也就是说是匹配方法的

除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

execution是一种使用频率比较高的切点指示符,它以方法的执行作为切入点。它可以说是最重要的,其它的指示符都是辅助它的。

比如通过如下表达式可以精确地匹配到Person类里的eat()方法

@Before("execution(* com.zx.aop1.Person.eat())")

如果要匹配Person类里的所有方法,可以使用通配符。

 @Before("execution(* com.zx.aop1.Person.*(..))")

第一个*表示返回值为任意类型,第二个*表示这个类里的所有方法,()括号表示参数列表,括号里的用两个点号表示匹配任意个参数,包括0个。

within

为了方便类型(如接口、类名、包名)过滤方法,AOP 提供了within关键字。其语法格式如下:

within(<type name>)

注意:within的粒度为类

匹配com.zx.aop1.person.Student类中的所有方法

    @Pointcut("within(com.zx.aop1.person.Student)")

匹配com.zx.aop1.person包及其子包中所有类中的所有方法

    @Pointcut("within(com.zx.aop1.person..*)")

匹配com.zx.aop1.person.Person类及其子类的所有方法

    @Pointcut("within(com.zx.aop1.person.Person+)")

匹配所有实现com.zx.aop1.person.Human接口的类的所有方法,包括接口方法和实现类的额外方法

    @Pointcut("within(com.zx.aop1.person.Human+)")
args

用于匹配当前执行的方法传入的参数 (args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用)

    @Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..)) && args(arg1)")
    private void pc1(String arg1){}

    /**
     * 通过args()传参数
     * @param arg1
     */
    @Before("pc1(arg1)")
    public void testArgs1(String arg1){
       Log.e(TAG, "testArgs1--- : "+arg1 );
    }

这种获取参数的方式不太灵活,而且开销大,所以可以用另外一种更加便捷的方式获取参数,可以通过joinPoint.getArgs()的方式去拿方法参数。比如上面例子,可以用如下方式实现:

 @Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..))")
    private void pc2(){}

    /**
     * 通过JoinPoint连接点的方式去拿方法参数
     * @param joinPoint
     */
    @Before("pc2()")
    public void testArgs2(JoinPoint joinPoint){
        for (Object arg : joinPoint.getArgs()) {
            Log.e(TAG, "testArgs2---: "+arg );
        }
    }
@within

语法:

@within(注解类型)

匹配所有使用了CheckWithin注解的类(注意是类)

    @Pointcut("@within(com.zx.aop1.CheckWithin)")

只能作用于类,不能是方法,也不能是接口。

@annotation

匹配使用了CheckAop注解的方法(注意是方法)

   @Pointcut("@annotation(com.zx.aop1.CheckAop)")
    private void aAnnotation1() {
    }

 @After("aAnnotation1()")
    public void testaAnnotation(JoinPoint joinPoint) {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        CheckAop checkAop = method.getAnnotation(CheckAop.class);
        Log.e(TAG, "testaAnnotation--: "+checkAop.value() );
    }

这里通过JoinPoint拿到对应的方法,再通过反射获取该方法的注解的值。此外还有一种简单的方法来获取注解值。

    @Pointcut(value = "@annotation(checkAop)")
    private void aAnnotation2(CheckAop checkAop) {
    }

 @After("aAnnotation2(checkAop)")
    public void testaAnnotation2(CheckAop checkAop) {
        Log.e(TAG, "testaAnnotation2---: "+checkAop.value() );
    }
target

用来匹配的链接点所属目标对象必须是指定类型的实例。

public interface Human {
    void setGender(int gender);
}

定义接口实现方法

public class Person implements Human {
    public int gender;

    @Override
    public void setGender(int gender) {
        this.gender = gender;
    }
}

定义调用代码

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Person  person = new Person();
        person.setGender(1);
    }
  }

Aspect定义1:target() && call()

    @Before("target(com.zx.aop1.person.Human) && call(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }
target&&call.png

Aspect定义2:target() && execution()

 @Before("target(com.zx.aop1.person.Human) && execution(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }
target && execution.png

结果分析:当使用target()时,不管用 execution()还是call(),Target对象都是Person,而this对象是不一样的。

this

用来匹配链接点所属的对象引用是某个特定类型的实例。

Aspect定义3:target() && call()

 @Before("this(com.zx.aop1.person.Human) && call(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }

结果:这里是没有任何日志输出的,因为这里是没有插入代码的。
原因待会儿再说。

Aspect定义4:this() && execution()

 @Before("this(com.zx.aop1.person.Human) && execution(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }
this && execution.png

结果分析:当使用this()时,如果用 call(),this对象是空,如果用execution()还是,this对象都是Person。原因也很简单,本案例的this对象只能是Human接口的实现类,而当你用用 call()时,插入代码是在MainActivity中。

targetthis总结:

1、target指代的是切点方法的所有者,而this指代的是被织入代码所属类的实例对象。

2、如果当前要代理的类没有实现某个接口就用 this;如果实现了某个接口,就使用 target

AspectJ之this和target的区别(四)

组合切点表达式
  • &&:要求连接点同时匹配两个切点表达式

  • ||:要求连接点匹配至少一个切入点表达式

  • !:要求连接点不匹配指定的切入点表达式

匹配所有实现Human接口的类的所有方法且方法的第一个参数为int类型

@Pointcut("within(com.zx.aop1.person.Human+) && execution(* com.zx.aop1.person...* *(int,..))")

更多切点指示符和切点表达式的应用,请关注后续文章!

写文不易,如果本文对你有所帮助,请点个关注

参考:
AOP 之 AspectJ 全面剖析 in Android
Android AspectJ详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容