非侵入式AOP监控之——AspectJ使用

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: > 一引言 > 二什么是AspectJ > 2.1 它只是一个代码编译器 > 2.2 它是用来做AOP编程的 > 2.3为什么要用AspectJ > 三AspectJ原理与运用 > 3.1 基本原理 > 3.2 使用方式 > 3.2.1 纯注解方式 > 3.2.2 AspectJ语言 > 3.2.3 结合自定义注解使用 > 四 使用AspectJ进行监听方法执行耗时 > 五

一引言
二什么是AspectJ
2.1 它只是一个代码编译器
2.2 它是用来做AOP编程的
2.3为什么要用AspectJ
三AspectJ原理与运用
3.1 基本原理
3.2 使用方式
3.2.1 纯注解方式
3.2.2 AspectJ语言
3.2.3 结合自定义注解使用
四 使用AspectJ进行监听方法执行耗时
五一些比较常见的问题
六推荐文章
Demo地址

一、引言

本博文的目的不是详细的介绍AspectJ的细节这是我来实习做的第一个任务因为老大最近让我用非侵入式的方法监测产品方法运行时间。查了很久有各种工具但都不好用也查到了DexPosed但是它在5.0以后就不兼容了因此没用它。最后找到了AspectJ学习之后成功监测了项目方法执行耗时并找出耗时方法。因此对其作了一些使用和重要概念上的总结。
相信很多做过Web的同学对AspectJ都不陌生Spring的AOP就是基于它而来的。如果说平常我们随便写写程序的时候基本也不会用到它需要调试的话无非就是多加一个System.out.printfln()或者Log.d()。但是由于基于面向对象的固有缺陷导致很多同模块、同一水平上的工作要在许多类中重复出现。比如说输出日志监控方法执行时间修改程序运行时的参数等等这样的事情其实它们的代码都是可以重用的。

如果在一个大型的项目当中使用手动修改源码的方式来达到调试、监控的目的第一需要插入许多重复代码打印日志监控方法执行时间代码无法复用第二修改的成本太高处处需要手动修改分分钟累死、眼花。

  • OOP: 面向对象把所有的事物都当做对象看待因此每一个对象都有自己的生命周期都是一个封装的整体。每一个对象都有自己的一套垂直的系列方法和属性使得我们使用对象的时候不需要太多的关系它的内部细节和实现过程只需要关注输入和输出这跟我们的思维方式非常相近极大的降低了我们的编写代码成本而不像C那样让人头痛。但在现实世界中并不是所有问题都能完美得划分到模块中。举个最简单而又常见的例子现在想为每个模块加上日志功能要求模块运行时候能输出日志。在不知道AOP的情况下一般的处理都是先设计一个日志输出模块这个模块提供日志输出API比如Android中的Log类。然后其他模块需要输出日志的时候调用Log类的几个函数比如e(TAG,...)w(TAG,...)d(TAG,...)i(TAG,...)等。
  • AOP: 面向对象编程固然是开启另一个编程时代但是久而久之也显露了它的缺点最明显的一点就是它无法横向切割某一类方法、属性当我们需要了解某一类方法、某一类属性的信息时就必须要在每一个类的方法里面即便他们是同样的方法只因是不同的类所以不同添加监控代码在代码量庞大的情况下这是一个不可取的方法。因此AOP编产生了基于AOP的编程可以让我们横向的切割某一类方法和属性不需要关心他是什么类别我觉得AOP并不是与OOP对立的而是为了弥补OOP的不足因为有了AOP我们的调试和监控就变得简单清晰。

二、什么是AspectJ

2.1 它只是一个代码编译器

AspectJ 意思就是Java的AspectJava的AOP。它其实不是一个新的语言它就是一个代码编译器ajc后面以此代替在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。因此ajc也可以编译Java代码。它在编译期将开发者编写的Aspect程序编织到目标程序中对目标程序作了重构目的就是建立目标程序与Aspect程序的连接耦合获得对方的引用获得的是声明类型不是运行时类型和上下文信息从而达到AOP的目的这里在编译期还是修改了原来程序的代码但是是ajc替我们做的。需要注意的就是AspectJ是在编译期重构的代码所以获得的对象是声明类型如果要获得运行时类型需要使用一些关键字this、target。

2.2 它是用来做AOP编程的

AspectJ就是AOP只不过是面向java的。AOP里面有一些重要基本的概念

  • aspect切面实现了cross-cutting功能是针对切面的模块。最常见的是logging模块、方法执行耗时模块这样程序按功能被分为好几层如果按传统的继承的话商业模型继承日志模块的话需要插入修改的地方太多而通过创建一个切面就可以使用AOP来实现相同的功能了我们可以针对不同的需求做出不同的切面。
  • jointpoint连接点连接点是切面插入应用程序的地方该点能被方法调用而且也会被抛出意外。连接点是应用程序提供给切面插入的地方在插入地建立AspectJ程序与源程序的连接。

    下面列表上的是被AspectJ认为是joinpoint的

下面列表上的是被AspectJ认为是joinpoint的

  • advice处理逻辑 advice是我们切面功能的实现它是切点的真正执行的地方。比如像写日志到一个文件中advice包括before、after、around等在jointpoint处插入代码到应用程序中。我们来看一看原AspectJ程序和反编译过后的程序。看完下面的图我们就大概明白了AspectJ是如何达到监控源程序的信息了。

    原Activity代码
    这里写图片描述

---

**Advise声明和定义**

这里写图片描述

---

**反编译后的原代码**

这里写图片描述

---

  • pointcut切点 pointcut可以控制你把哪些advice应用于jointpoint上去通常你使用pointcuts通过正则表达式来把明显的名字和模式进行匹配应用。决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字具体含义见第四节

这里写图片描述

顺便再附上一些切点匹配规则
1匹配方法

@注解 访问权限 返回值的类型 包名.函数名(参数)  
  @注解和访问权限public/private/protect以及static/final属于可选项。如果不设置它们则默认都会选择。以访问权限为例如果没有设置访问权限作为条件那么publicprivateprotect及static、final的函数都会进行搜索。  
  返回值类型就是普通的函数的返回值类型。如果不限定类型的话就用*通配符表示  
  包名.函数名用于查找匹配的函数。可以使用通配符包括*和..以及+号。其中*号用于匹配除.号之外的任意字符而..则表示任意子package+号表示子类。  
  比如  
  java.*.Date可以表示java.sql.Date也可以表示java.util.Date  
  Test*可以表示TestBase也可以表示TestDervied  
  java..*表示java任意子类  
  java..*Model+表示Java任意package中名字以Model结尾的子类比如TabelModelTreeModel  
  等  
  最后来看函数的参数。参数匹配比较简单主要是参数类型比如  
  (int, char)表示参数只有两个并且第一个参数类型是int第二个参数类型是char  
  (String, ..)表示至少有一个参数。并且第一个参数类型是String后面参数类型不限。在参数匹配中  
  ..代表任意参数个数和类型  
  (Object ...)表示不定个数的参数且类型都是Object这里的...不是通配符而是Java中代表不定参数的意思  

2匹配构造函数

Constructorsignature和Method Signature类似只不过构造函数没有返回值而且函数名必须叫new。比如  
public *..TestDerived.new(..)  
  public选择public访问权限  
  *..代表任意包名  
  TestDerived.new代表TestDerived的构造函数  
  (..)代表参数个数和类型都是任意  
再来看Field Signature和Type Signature用它们的地方见图5。下面直接上几个例子  
Field Signature标准格式  
@注解 访问权限 类型 类名.成员变量名  
  其中@注解和访问权限是可选的  
  类型成员变量类型*代表任意类型  
  类名.成员变量名成员变量名可以是*代表任意成员变量  
比如  
set(inttest..TestBase.base)表示设置TestBase.base变量时的JPoint  
Type Signature直接上例子  
staticinitialization(test..TestBase)表示TestBase类的static block  
handler(NullPointerException)表示catch到NullPointerException的JPoint。

2.3、为什么要用AspectJ

1、非侵入式监控 可以在不修监控目标的情况下监控其运行截获某类方法甚至可以修改其参数和运行轨迹
2、学习成本低 它就是Java只要会Java就可以用它。
3、功能强大可拓展性高 它就是一个编译器+一个库可以让开发者最大限度的发挥实现形形色色的AOP程序


三、AspectJ原理与运用

先放一块AspectJ代码这里使用的都是AspectJ较为常用的知识接着在解释。


import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Path;
import android.os.Build;

import org.android10.gintonic.internal.ChooseDialog;
import org.android10.gintonic.internal.DebugLog;
import org.android10.gintonic.internal.MethodMsg;
import org.android10.gintonic.internal.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * 截获类名最后含有Activity、Layout的类的所有方法
 * 监听目标方法的执行时间
 */
@Aspect
public class TraceAspect {
  private static Object currentObject = null;
  //进行类似于正则表达式的匹配被匹配到的方法都会被截获
  ////截获任何包中以类名以Activity、Layout结尾并且该目标类和当前类是一个Object的对象的所有方法
  private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";
   //精确截获MyFrameLayou的onMeasure方法
    private static final String POINTCUT_CALL = "call(* org.android10.viewgroupperformance.component.MyFrameLayout.onMeasure(..))";
    
  private static final String POINTCUT_METHOD_MAINACTIVITY = "execution(* *..MainActivity+.onCreate(..))";
  
  //切点ajc会将切点对应的Advise编织入目标程序当中
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}
  @Pointcut(POINTCUT_METHOD_MAINACTIVITY)
  public void methodAnootatedWith(){}

    /**
     * 在截获的目标方法调用之前执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Before("methodAnootatedWith()")
  public void onCreateBefore(JoinPoint joinPoint) throws Throwable{
      Activity activity = null;
      //获取目标对象
      activity = ((Activity)joinPoint.getTarget());
      //插入自己的实现控制目标对象的执行
      ChooseDialog dialog = new ChooseDialog(activity);
      dialog.show();
      
      //做其他的操作
      buildLogMessage("test",20);
  }
    /**
     * 在截获的目标方法调用返回之后无论正常还是异常执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
 @After("methodAnootatedWith()")
  public void onCreateAfter(JoinPoint joinPoint) throws Throwable{
      Log.e("onCreateAfter:","onCreate is end .");

  }
    /**
     * 在截获的目标方法体开始执行时刚进入该方法实体时调用
     * @param joinPoint
     * @return
     * @throws Throwable
     */
  @Around("methodAnnotated()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

    if (currentObject == null){
        currentObject = joinPoint.getTarget();
    }
      //初始化计时器
    final StopWatch stopWatch = new StopWatch();
      //开始监听
      stopWatch.start();
      //调用原方法的执行。
    Object result = joinPoint.proceed();
      //监听结束
    stopWatch.stop();
      //获取方法信息对象
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String className;
      //获取当前对象通过反射获取类别详细信息
      className = joinPoint.getThis().getClass().getName();

      String methodName = methodSignature.getName();
    if (currentObject != null && currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
    }else if(currentObject != null && !currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
        currentObject = joinPoint.getTarget();
        DebugLog.outPut(new Path());    //日志存储
        DebugLog.ReadIn(new Path());    //日志读取
    }
    return result;
  }

  /**
   * 创建一个日志信息
   *
   * @param methodName 方法名
   * @param methodDuration 执行时间
   * @return
   */
  private static String buildLogMessage(String methodName, long methodDuration) {
    StringBuilder message = new StringBuilder();
    message.append(methodName);
    message.append(" --> ");
    message.append("[");
    message.append(methodDuration);
    if (StopWatch.Accuracy == 1){
        message.append("ms");
    }else {
        message.append("mic");
    }
    message.append("]      ");

    return message.toString();
  }

}

3.1 基本原理

在编译期对目标对象、方法做标记对目标类、方法进行重构将PointCut插入目标中截获该目标的信息以及上下文环境以达到非侵入代码监控的目的——注意它只能获得对象的声明如果对象的声明式接口那么默认情况下不使用this、target约束切点获取的是声明类型而不是具体运行时的类。

  1. 编写Aspect声明Aspect、PointCut和Advise。
  2. ajc编织 AspectJ编译器在编译期间对所切点所在的目标类进行了重构在编译层将AspectJ程序与目标程序进行双向关联生成新的目标字节码即将AspectJ的切点和其余辅助的信息类段插入目标方法和目标类中同时也传回了目标类以及其实例引用。这样便能够在AspectJ程序里对目标程序进行监听甚至操控。
  3. execution 顾名思义它截获的是方法真正执行的代码区Around方法块就是专门为它存在的。调用Around可以控制原方法的执行与否可以选择执行也可以选择替换。
//截获任何包中以类名以Activity、Layout结尾并且该目标类和当前类是一个Object的对象的所有方法
private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";
  //基于execution的切点
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}

4 . call 同样从名字可以看出call截获的是方法的调用区它并不截获代码真正的执行区域它截获的是方法调用之前与调用之后与before、after配合使用在调用方法的前后插入JoinPoint和before、after通知。它截获的信息并没有execution那么多它无法控制原来方法的执行与否只是在方法调用前后插入切点因此它比较适合做一些轻量的监控方法调用耗时方法的返回值等。

 //精确截获MyFrameLayou的onMeasure方法
    private static final String POINTCUT_CALL = "call(* org.android10.viewgroupperformance.component.MyFrameLayout.onMeasure(..))";
    //基于call的切点
    @Pointcut(POINTCUT_METHOD_MAINACTIVITY)
  public void methodAnootatedWith(){}

5 . Around替代原理目标方法体被Around方法替换原方法重新生成名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行是这样达到替换原方法的目的。达到这个目的需要双方互相引用桥梁便是Aspect类目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure闭包类该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息同时该类作为切点原方法的执行代理该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的便可以在Aspect程序中监控和修改目标程序。

/**
     * 在截获的目标方法体开始执行时刚进入该方法实体时调用
     * @param joinPoint
     * @return
     * @throws Throwable
     */
  @Around("methodAnnotated()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

    if (currentObject == null){
        currentObject = joinPoint.getTarget();
    }
      //初始化计时器
    final StopWatch stopWatch = new StopWatch();
      //开始监听
      stopWatch.start();
      //调用原方法的执行。
    Object result = joinPoint.proceed();
      //监听结束
    stopWatch.stop();
      //获取方法信息对象
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String className;
      //获取当前对象通过反射获取类别详细信息
      className = joinPoint.getThis().getClass().getName();

      String methodName = methodSignature.getName();
    if (currentObject != null && currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
    }else if(currentObject != null && !currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
        currentObject = joinPoint.getTarget();
        DebugLog.outPut(new Path());    //日志存储
        DebugLog.ReadIn(new Path());    //日志读取
    }
    return result;
  }

---
6 . Before与After Before与After只是在方法被调用前和调用之后添加JoinPoint和通知方法直接插入原程序方法体中,调用AspectJ程序定义的Advise方法它并不替代原方法是在方法call之前和之后做一个插入操作。After分为returnning和throwing两类前者是在正常returning之后调用后者是在throwing发生之后调用。默认的After是在finally处调用因此它包含了前面的两种情况。

   /**
     * 在截获的目标方法调用之前执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Before("methodAnootatedWith()")
  public void onCreateBefore(JoinPoint joinPoint) throws Throwable{
      Activity activity = null;
      //获取目标对象
      activity = ((Activity)joinPoint.getTarget());
      //插入自己的实现控制目标对象的执行
      ChooseDialog dialog = new ChooseDialog(activity);
      dialog.show();
      
      //做其他的操作
      buildLogMessage("test",20);
  }
    /**
     * 在截获的目标方法调用返回之后无论正常还是异常执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
 @After("methodAnootatedWith()")
  public void onCreateAfter(JoinPoint joinPoint) throws Throwable{
      Log.e("onCreateAfter:","onCreate is end .");

  }

7 . 重要关键字
在其它关键字中必须要注意的就是this、target的使用和区别同时还有一个很重要的方法Signature.getDeclaringType();AspectJ是在编译期截获的对象信息因此它获得的标签只是对象的声明比如接口、抽象类而不是运行时具体的对象。如果想要获得运行时对象就需要用this、target关键字

this  用于匹配当前AOP代理对象类型的执行方法注意是AOP代理对象的类型匹配这样就可能包括引入接口也类型匹配
target用于匹配当前目标对象类型的执行方法注意是目标对象的类型匹配这样就不包括引入接口也类型匹配
args用于匹配当前执行的方法传入的参数为指定类型的执行方法
within用于匹配指定类型内的方法执行

更加详细的解说请参考深入理解Android之AOP该博文对于AspectJ的其他详细概念、定义、细节示例解说的非常清楚如果想要详细了解请务必要看。


3.2 使用方式

3.2.1 纯注解方式

上面贴的代码就是该方式也是最普遍的方式它不需要其他插件的支持Eclipse中有AJDT可以支持AspectJ关键字声明但Android Studio中没有改插件使用Java的注解和ajc以及它的库就可以完成AOP编程非常方便而且可以在绝大部分支持Java的IDE中使用。缺点就是对于注释部分的匹配没有检错功能。


/**
 * Created by lingyi.mly on 2016/5/21.
 */
@Aspect
public class TraceAspect3 {
    private static volatile Object currentObject = null;
    private ExecutorService ThreadPool = Executors.newFixedThreadPool(10);
    private static final String POINTCUT_METHOD =
            "call(* *.*(..))&&target(Object) &&!within(*.TimeMonitorFragment)";
    @Pointcut(POINTCUT_METHOD)
    public void methodAnnotated() {
    }

    StopWatch stopWatch;
    MethodSignature methodSignature;
    String methodName;
    String className;

    @Before("methodAnnotated()")
    public void beforeInvoked(final JoinPoint joinPoint) {
        className = "call target: " + joinPoint.getTarget().getClass().getName();
        methodSignature = (MethodSignature) joinPoint.getSignature();
        methodName = methodSignature.getName();
        stopWatch = new StopWatch();
        stopWatch.start();
    }

    @After("methodAnnotated()")
    public void afterInvoked(final JoinPoint joinPoint) {
        stopWatch.stop();
        double methodDuration = stopWatch.getTotalTime(StopWatch.Accuracy);
        DebugLog.log(new MethodMsg(className, methodName, methodDuration, StopWatch.Accuracy));
    }
}

3.2.2 AspectJ语言

在Eclipse中使用AJDT插件,可以识别AspectJ的语法。这样编写起来相对于注解要方便许多还提供检错功能比较强大。不过不是所有的IDE都支持比如Android Studio目前就没有我哭了好久。

package main;
import java.util.HashMap;
import java.util.Map;
/**
 * 只有call才能区分this 与target 在与的情况下两者不共存在交的情况下共存。
 * execution匹配this 与 target时无论是 与 还是 交集 都是同一个对象
 * @author lingyi.mly
 *
 */


public aspect Aspect{
    static int count = 0;
    pointcut targetTest() : call(* main.*.*(..)) &&( target(Object) );
    pointcut thisTest( ) : execution(* main.*.*(..)) && (target(Object) ||this(Object));
    Object around() : thisTest() {
        if (thisJoinPoint.getThis() != null) {
            System.out.println(thisJoinPoint.getThis().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        }else if (thisJoinPoint.getTarget() != null) {
            System.out.println(thisJoinPoint.getTarget().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        } 
        return null;
    }
    before() : targetTest() {
        if (thisJoinPoint.getThis() != null) {
            System.out.println("this:  "+thisJoinPoint.getThis().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        }else if (thisJoinPoint.getTarget() != null) {
            System.out.println("target:  "+thisJoinPoint.getTarget().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        } 
    }

    private static Map<String, Integer> threadMap = new HashMap<String,Integer>();
}

3.2.3 结合自定义注解使用

这个是混合用法可以在execution、call中使用注解然后该注解标注在目标方法上就可以实现关联并且截获。这样做的好处实在想不到最多就是可以精确定位到某一个方法那使用绝对路径匹配不也可以。而且还侵入了源码。实在是不推荐不过我在网上看到有人这么用了所以也贴上来了。如果哪位高手知道这样做的精髓请一定指教。下面贴一下它的用法实现。

自定义注解及被标记的方法

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 表明被注释的方法将被跟踪仅在Debug模式下并且将会与Aspect程序中截获该注释的Advise关联调用该切点
 * 的Advise
 */
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface DebugTrace {}



/**
 * 被注解的类
 */
public class MyFrameLayout extends FrameLayout {
  //........  
  //被注解的方法
  @DebugTrace
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }
  @DebugTrace
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
  }
}

切面

package org.android10.gintonic.aspect;

/**
 * 跟踪被DebugTrace注解标记的方法和构造函数
 */
@Aspect
public class TraceAspect {
  //跟踪DebugTrace注解
  private static final String POINTCUT_METHOD =
      "execution(@org.android10.gintonic.annotation.DebugTrace * *(..))";

  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotatedWithDebugTrace() {}
  
  @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    // Do SomeThing
    stopWatch.start();
    Object result = joinPoint.proceed();
    stopWatch.stop();
    // Do SomeThing
    return result;
  }
  // ........省略
}

四、DEMO——监听方法执行耗时打印并输出

源程序代码

AspectJDemo

关键代码

private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";
  // ...........
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}
  // .........

  @Around("methodAnnotated()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

    if (currentObject == null){
        currentObject = joinPoint.getTarget();
    }
      //初始化计时器
    final StopWatch stopWatch = new StopWatch();
      //开始监听
      stopWatch.start();
      //调用原方法的执行。
    Object result = joinPoint.proceed();
      //监听结束
    stopWatch.stop();
      //获取方法信息对象
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String className;
      //获取当前对象通过反射获取类别详细信息
      className = joinPoint.getThis().getClass().getName();

      String methodName = methodSignature.getName();
      String msg =  buildLogMessage(methodName, stopWatch.getTotalTime(1));
    if (currentObject != null && currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className,msg,stopWatch.getTotalTime(1)));
    }else if(currentObject != null && !currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, msg,stopWatch.getTotalTime(1)));
        Log.e(className,msg);
        currentObject = joinPoint.getTarget();
//        DebugLog.outPut(new Path());    //日志存储
//        DebugLog.ReadIn(new Path());    //日志读取
    }
    return result;
  }

监听方法执行时间

TimeMonitor:: org.android10.viewgroupperformance.activity.RelativeLayoutTestActivity     onCreate --> [8.636ms]                                                                                                                                                                 
org.android10.viewgroupperformance.activity.MainActivity     openActivity --> [6.561ms]                                                                                                                                                         
org.android10.viewgroupperformance.activity.MainActivity     mapGUI --> [0.061ms]      

五、一些比较常见的问题

1问题AspectJ中Signature提供的getDeclareType返回的是声明类型无法获取运行时类型因此无法准确获取接口运行时类别。

方案使用target关键字约束pointCut获取目标对象通过反射获取其运行时类别。

2问题使用target关键字约束pointcut获取目标对象Object之后无法获取静态方法不属于对象

方案单独将静态方法提出来再与前面的target关键字约束的集合取并集。

  "execution(* *.*(..))&&(target(Object)||execution(static * *.*(..)))&&!within(TimeMonitorFragment)";

3问题使用Before、After通知测试方法耗时的精确度误差较大

方案改用execution+around。两点第一由于Before、After是在原方法调用前后插入通知会影响本来所在方法快的执行速率第二同时Before、After两个操作无法保证是原子操作多线程情况下会有误差。因此该用execution关键字截获方法体的真正执行处使用Around通知替代原方法原方法被更名但结构不变在Around通知体内调用原方法计时这样能够真正还原方法执行耗时


六、推荐学习资源

深入理解Android之AOP
官方英文文档
跟我学习AspectJ系列

Demo地址 AspectJDemo

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
12月前
|
XML Java 数据格式
Spring中使用AspectJ实现AOP的五种通知
Spring中使用AspectJ实现AOP的五种通知
188 1
Spring中使用AspectJ实现AOP的五种通知
|
Java Spring
11Spring - 基于AspectJ的AOP开发(注解的方式)
11Spring - 基于AspectJ的AOP开发(注解的方式)
46 0
|
2月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
57 1
|
22天前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
43 2
|
3月前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
4月前
|
XML 监控 Java
Java中的AOP编程:AspectJ与Spring AOP的应用
Java中的AOP编程:AspectJ与Spring AOP的应用
|
5月前
|
Java 编译器 Maven
Java一分钟之-AspectJ:AOP库
【6月更文挑战第13天】AspectJ是Java的AOP框架,扩展了语言并提供编译时和加载时织入,便于模块化横切关注点。关键概念包括编织、切面、切点和通知。常见问题涉及编译时织入配置、切点表达式误用、异常处理和版本兼容性。通过正确配置构建工具、精准设计切点、妥善处理异常和确保版本兼容,可避免这些问题。文中还提供了一个记录服务层方法执行时间的代码示例,帮助读者快速上手AspectJ。
136 2
|
6月前
|
Java 开发者 Spring
Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
【5月更文挑战第1天】Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
68 5
|
6月前
|
Java
SpringBoot整合AOP整合aspectj实现面向切面编程实现参数接收和请求时间打印
SpringBoot整合AOP整合aspectj实现面向切面编程实现参数接收和请求时间打印
28 0
|
6月前
|
Java 编译器 程序员
Spring AOP 和 AspectJ 的比较
Spring AOP 和 AspectJ 的比较
228 0