AOP埋点从入门到放弃(二)

其实人最大悲哀莫过于知道本身想要什么,殊不知道怎么坚持!最近迷恋上了死侍 其实和我平时的状态差很少,以一个混子的心态去作任何事情,每每成功的几率会更大!!!java

一张图片镇楼!!!android

上文说到了AspectJ的集成问题,若是没有看过上一篇文章的小伙伴能够看看本系列的第一篇文章。git

AOP埋点从入门到放弃(一)github

这篇文章充分的讲解了关于AspectJ的集成问题,接下来咱们讲讲怎么更好的使用AspectJ来惟我所用。。。编程

1. 一些乱七八糟东西的解释

其实我感受这个东西提及来是最难的,由于要记住一大堆概念!其实记忆力是个人最烦的东西,可是我是一只猿,一只牛逼的猿!因此当背课文了...bash

先来看一段代码app

@Aspect
public class TraceAspect {
    private static final String TAG = "hjl";

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore: 切面的点执行了!" + key);
    }
}
复制代码

先说一下这段代码实现了什么?主要实现了在Activity中全部以on开头的方法前面打印一段**Log.e(TAG, "onActivityMethodBefore: 切面的点执行了!" + key);**代码!接下来咱们来逐一讲解!框架

1.1 顶部@Aspect的含义

关于@Aspect这个注解,是下面全部内容的基础,若是没有这个注解AspectJ没有相应的入口,就不会有相应的切面了!AspectJ会找到全部@Aspect注解,而后函数

1.2 通配符的概念(Pointcut语法)(难点)

首先说一下通配符的大致格式:(我也不知道理解的对不对,可是项目中使用的时候没有发现什么不对的地方)post

@注解 访问权限 返回值类型 类名 函数名 参数

大致上的通配符都是这个格式的,咱们用上面的一个通配符去说明一下:

execution(* android.app.Activity.on*(..))
复制代码

execution 通常指定方法的执行,在日后由于没有注解和访问权限的限制,因此这里什么也没写,返回值用*代替,说明能够用任何返回值,android.app.Activity.on*表明函数名称的全路径,后面的一个型号表明on后面能够接任何东西,后面的(..)表明其参数能够为任何值。

上面就是一段通配符的含义了!其实学习AspectJ的时候,我以为最难懂的就是相应的操做符了,若是操做符弄明白了的话,真的就很简单了!可是若是以前作事后台的话,这个应该就很简单了,就是Spring框架中的AOP是同样的都是用的Pointcut语法。由于本身不是java后台开发人员,因此解释的可能不到位,你能够去找大家java后台组的人去问问,学习一下!应该比我讲的强不少,由于我真是第一次接触这个东西!

由于平时没接触过,因此这里就写一些经常使用的吧!

分类

JPoint 说明 Pointcut语法说明
method execution 通常指定方法的执行 execution(MethodSignnature)
method call 函数被调用 call(MethodSignnature)
constructor call 构造函数被调用 call(ConstructorSignature)
constructor execution 构造函数执行内部 execution(ConstructorSignature)
field get 读变量 get(FieldSIgnature)
field set 写变量 set(FieldSIgnature)
handler 异常处理 handler(TypeSignature) 注意:只能和@Before()配合使用,不支持@After、@Around等
advice execution advice执行 adciceexectuin()

Signature参考

Sigbature 语法(间隔一个空格)
MethodSignature @注解 访问权限 返回值类型 类名.函数名(参数)
ConstructorSignature @注解 访问权限 类名.new(参数)
FieldSignature @注解 访问权限 变量类型 类名.类成员变量名

Signature语法明细

Sigbature语法明细 解释
@注解 @完整类名,若是没有则不写
访问权限 public/private/portect,以及static/final,若是没有则不写 注意:若是只写public则匹配出来的是所有,若是写public final则匹配的是全部public final开头的内容
返回值类型 若是不限定类型,使用通配符*表示
类名.函数名 可使用的通配符,包括*和..以及+号。其中*号用于陪陪除.号以外的任意字符,而..则表示任意字package,+号表示子类 注意:1.ConstructorSignature的函数名只能为new 2.(.函数名能够不写),重用和注解一块儿使用 3.不能以..开头
变量类型 成员变量类型,*表明任意类型
类名.成员变量名 类名可使用通配符,与函数。函数名相似

Advice内容

Advice 说明
@Before(Pointcut) 执行在jPoint以前
@After(Pointcut) 执行在jPoint以后
@Around(Pointcut) 替代原来的代码,若是要执行原来的代码,须要使用proceedingJoinPoint.proceed(); 注意:不能够和@After和@Before等一块儿使用

上面这写表的你先简单看一下,估计你一会仍是会回来看的!!!

1.2.1 method->call的示例:

@Pointcut("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
    public void callMethod() {
        //为了演示call方法的使用
    }

    @Before("callMethod()")
    public void beforeCallMethod(JoinPoint joinPoint) {
        Log.e(TAG, "call方法的演示");
    }
复制代码

说一下上面代码:@Pointcut是来注解方法的,call后面添加了一系列的通配符,简单说就是一个方法的地址,*表明没有返回值,@Before是说明在切片前执行!这里千万别把com.jinlong.aspectjdemo.MainActivity.callMethod这个地址写错了就行!

其实上面这段代码能够简化为

@Before("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
    public void beforeCallMethod(JoinPoint joinPoint){
        Log.e(TAG, "call方法的演示");
    }
复制代码

若是你把上面的@Before换为@After,那么就会在方法以后打印!!!

再来看一段代码:

@Around("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
    public void aroundCallMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        Log.e(TAG, "方法执行的时间为" + (endTime - startTime));
    }
复制代码

这段代码是统计方法执行时间的,这里着重讲两个内容

  • joinPoint.proceed();还原你原来的代码,若是没有这句,你原来的代码就没了!没了!
  • @Around 替代原来的代码,加上上面这句就能够还原了以前的代码了!

因此这里就能计算出方法的执行时间了!!!也就是@Around的用法了!

1.2.2 method->execution 的示例:

以前的时候,我总以为execution和call是同样的,可是后来我知道了,他们最大的区别是这样的!!!

好比你有一个方法:callMethod()对吧! 而后使用call是这个样子滴~

call的相应方法();
callMethod();
复制代码

可是若是是execution的话就编程这个样子滴了~

callMethod(){
   execution的相应方法(); 
}
复制代码

其余的就没有什么区别了,也就不在这里举例说明了。

1.2.3 构造方法的操做

先说下这个东西是构造方法上用的,也就是说针对于相应的构造方法进行相应切面操做的!可是我一只有一个疑问不明白,若是我按照上面方法的通配符进行操做的话,按照常理说应该也是能在相应切面进行操做的才对啊!编译不报错,但就是怎么也打印不出来结果,还请明白的大神帮我解答一下!

按照上面的表格还有一种方案解决相应构造方法的问题

@Before("execution(com.jinlong.aspectjdemo.Person.new(..))")
    public void beforeConstructorExecution(JoinPoint joinPoint) {
        //这个是显示Constructor的
        Log.e(TAG, "before->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
    }
复制代码

这段代码我尝试过了,能够打印出结果。也就是以**ConstructorSignature @注解 访问权限 类名.new(参数)**这种方式就能够打印出相应结果来!不过我劝你把那个@Before换成@After不然你打印出来的内容多是一个空!

1.2.4 关于相应成员变量的问题

就是至关你能够修改类的成员变量,无论你怎么设置最终返回的都是你设定的值!

看下面这段代码:

这里是正常的一个类:

public class Person {
    private String name;
    private String age;

    public Person() {
    }

    public Person(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
复制代码

这才是核心代码!

@Around("get(String com.jinlong.aspectjdemo.Person.age)")
    public String aroundFieldGet(ProceedingJoinPoint joinPoint) throws Throwable {
        // 执行原代码
        Object obj = joinPoint.proceed();
        String age = obj.toString();
        Log.e(TAG, "age: " + age);
        return "100";
    }
复制代码

这里用到的的是上面FieldSignature @注解 访问权限 变量类型 类名.类成员变量名上面这段代码的含义是这样滴,在每次用到age这个内容的时候,都会被修改,可是有一个问题,就是你不能重写类的**toString()**方法,若是你重写了这个方法的话,obj返回的就是一个空!我还真不知道为何,还请明白的告知一二!,这里面get是一个关键字,就这么理解吧!由于没有设置访问权限和注解,因此这里直接就省返回的变量类型(String类型)和类成员变量名的全路径了!

在看下面这段代码:

@Around("set(String com.jinlong.aspectjdemo.Person.age)")
    public void aroundFieleSet(ProceedingJoinPoint joinPoint) {
        Log.e(TAG, "aroundFieleSet: " + joinPoint.getTarget().toString() +
                joinPoint.getSignature().getName());
    }
复制代码

这个能够对相应的age属性进行设置的方法,也就是当发生赋值操做的时候都会被修改!这里和你们说明一下,上面这段代码没有**joinPoint.proceed();**代码,因此以前的代码中执行的内容就会失效了!也就是说被打印这段话替换了!其实上面这段代码你运行的时候你会发现一件事,Log被打印了两次,为何呢?你想啊!this.age = age;在set方法中出现一次,并且还在构造方法中出现一次呢。仔细看看,因此这里要排除构造方法总的那一次,怎么处理呢?就要用到 withincode了!

1.2.5 withincode排除内容

怎么理解这个东西呢?表示某个构造方法或函数中涉及到的JPoint。不理解吧!不要紧,看一段代码你就理解了!

@Around("set(String com.jinlong.aspectjdemo.Person.age)&&!withincode(com.jinlong.aspectjdemo.Person.new(..))")
    public void aroundFieleSet(ProceedingJoinPoint joinPoint) throws Throwable {
        //设置相应的成员变量
        joinPoint.proceed();
    }
复制代码

在1.2.4上面说到set会在两个地方都有,可是其实我是不想要构造方法中的那个的,怎么把他排除呢?那就是后面添加的这句代码**com.jinlong.aspectjdemo.Person.new(..))**就是把构造方法中的内容排除!其实很好理解,就是排除相应的构造方法,能够简单理解withincode就是带着某个内容,可是因为取反了,因此就是不带着这个东西了!!!就酱紫了。。。

1.2.5 handler的异常的捕捉

这个相比较之下就简单一点了,直接上代码:

这是在代码中的一个异常,很简单的一个异常,若是这个方法走了相应的catch,那么就能捕获相应的异常了!

private void catchMethod() {
        try {
            int sum = 100 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

关键代码来了。。。

@Before("handler(java.lang.Exception)")
    public void handlerMethod() {
        Log.e(TAG, "handlerMethod: 异常产生了");
    }
复制代码

是否是很简单,使用一个handler关键字,加上一个异常的全路径,ok搞定,可是这里必定要注意,前面的注解只能是@Before,切记!!!

1.2.6 关于注解的使用

有许多第三方你是不知道具体方法名称的,可是你还想使用的话怎么办?那就是注解了,由于注解能够很好的解决这种需求。

再来看一段代码:

定义一段注解内容:

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
复制代码

由于注解的这些内容不是本篇文章的重点,因此这里不许备讲解了。感兴趣的你能够百度一下,这个注解主要是在编译完成后也会起做用,而且是方法和成员变量均可以使用!

在加上下面这段代码就能够进行相应切面的操做了!

@Pointcut("execution(@com.jinlong.aspectjdemo.DebugTrace * *(..))")
    public void DebugTraceMethod() {
    }

    @Before("DebugTraceMethod()")
    public void beforeDebugTraceMethod(JoinPoint joinPoint) {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "注解这个方法执行了: ");
    }
复制代码

看到在这段代码你们应该不怎么陌生了,就是在方法内容添加相应的切面操做。最后在使用的方法的上面添加相应的注解就能够了!就这么简单!

@DebugTrace
    private void mothod1() {
        Log.e(TAG, "方法1执行了");
    }
复制代码

上面基本上包含了咱们APP使用中,能用到一些内容,若是有什么讲的不到位的地方还请指出。由于是第一次接触这个东西,可能有些细节讲解的不是很到位,还请谅解!!!

想看源码吗?想看连接吗?点这里

相关文章
相关标签/搜索