AOP之@AspectJ技术原理详解

一、AOP

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑
各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1.1 主要功能

日志记录,性能统计,安全控制,事务处理,异常处理等等。

1.2 主要目标

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变
这些行为的时候不影响业务逻辑的代码。

1.3 适用对象

比较大型的项目,而且迭代较快,使用OOP太消耗内力。
有日志、性能、安全、异常处理等横切关注点需求。

1.4 AOP与OOP的关系

OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。但是也有它的缺点,最明显的就是关注点聚焦时,面向对象无法简单的解决这个问题,一个关注点是面向所有而不是单一的类,不受类的边界的约束,因此OOP无法将关注点聚焦来解决,只能分散到各个类中。
AOP(面向切面编程)则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
AOP并不是与OOP对立的,而是为了弥补OOP的不足。OOP解决了竖向的问题,AOP则解决横向的问题。因为有了AOP我们的调试和监控就变得简单清晰。它们之间的关系如下图所示:

《AOP之@AspectJ技术原理详解》

1.4.1 对比一——单一横切关注点
《AOP之@AspectJ技术原理详解》

《AOP之@AspectJ技术原理详解》

1.4.2 对比二——多横切关注点
《AOP之@AspectJ技术原理详解》

结论:
《AOP之@AspectJ技术原理详解》

二、Android中使用@AspectJ

AspectJ 意思就是Java的Aspect,Java的AOP。它其实不是一个新的语言,它的核心是ajc(编译器)\weaver(织入器)。

  • ajc编译器:基于Java编译器之上的,它是用来编译.aj文件,aspectj在Java编译器的基础上增加了一些它自己的关键字和方法。因此,ajc也可以编译Java代码。
  • weaver织入器:为了在java编译器上使用AspectJ而不依赖于Ajc编译器,aspectJ 5出现了@AspectJ,使用注释的方式编写AspectJ代码,可以在任何Java编译器上使用。

由于AndroidStudio默认是没有ajc编译器的,所以在Android中使用@AspectJ来编写(包括SpringAOP也是如此)。它在代码的编译期间扫描目标程序,根据切点(PointCut)匹配,将开发者编写的Aspect程序编织(Weave)到目标程序的.class文件中,对目标程序作了重构(重构单位是JoinPoint),目的就是建立目标程序与Aspect程序的连接(获得执行的对象、方法、参数等上下文信息),从而达到AOP的目的。

2.1 Gradle 配置示例

要引入AspectJ到Android工程中,最重要的就是两个包:

//在buildscript中添加该编织器,gradle构建时就会对class文件进行编织
classpath 'org.aspectj:aspectjweaver:1.8.9'
//在dependencies中添加该依赖,提供@AspectJ语法
compile 'org.aspectj:aspectjrt:1.8.9'

此外还有一个工具包,用于Gradle构建时进行打日志等操作:

//在buildscript中添加该工具包,在构建工程的时候执行一些任务:打日志等
classpath 'org.aspectj:aspectjtools:1.8.9'import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

//打印gradle日志
android.libraryVariants.all { variant ­>
LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
  JavaCompile javaCompile = variant.javaCompile
  javaCompile.doLast {
  String[] args = ["­showWeaveInfo",
     "­1.5",
     "­inpath", javaCompile.destinationDir.toString(),
     "­aspectpath", javaCompile.classpath.asPath,
     "­d", javaCompile.destinationDir.toString(),
     "­classpath", javaCompile.classpath.asPath,
     "­bootclasspath", 
     project.android.bootClasspath.join(
     File.pathSeparator)]
  MessageHandler handler = new MessageHandler(truenew Main().run(args, handler)
  def log = project.logger
  for (IMessage message : handler.getMessages(null, true)) {
  switch (message.getKind()) {
    case IMessage.ABORT:
    case IMessage.ERROR:
    case IMessage.FAIL:
      log.error message.message, message.thrown
    break;
    case IMessage.WARNING:
    case IMessage.INFO:
      log.info message.message, message.thrown
    break;
    case IMessage.DEBUG:
      log.debug message.message, message.thrown
    break;
   }
 }
}
}

附:美团RoboAspectJ

buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'com.meituan.gradle:roboaspectj:0.9.2'
classpath 'jaop.gradle.plugin:gradle­plugin:1.0.2'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}

配置参数

// AspectJ
aspectj {
disableWhenDebug true
javartNeeded true
// 排除不需要AOP扫描的包
exclude group: 'xxxx', module: 'xxxx'
compileOptions {
defaultJavaVersion = JavaVersion.VERSION_1_7
}
}

2.2 基本概念

2.2.1 切面——Aspect

实现了cross­cutting功能,是针对切面的模块。最常见的是logging模块、方法执行耗时模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话需要插入修改的地方太多,而通过创建一个切面就可以使用AOP来实现相同的功能了,我们可以针对不同的需求做出不同的切面。

2.2.2 连接点——JoinPoint

连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,可以添加新的方法。比如:我们的切点可以认为是findInfo(String)方法。
AspectJ将面向对象的程序执行流程看成是JoinPoint的执行链,每一个JoinPoint是一个单独的闭包,在执行的时候将上下文环境赋予闭包执行方法体逻辑。
下面列表上的是被AspectJ认为是joinpoint的:
《AOP之@AspectJ技术原理详解》

2.2.3 切点——PointCut

切点的声明决定需要切割的JoinPoint的集合,就结果上来说,它是JoinPoint的一个实际子集合。
pointcut可以控制你把哪些advice应用于jointpoint上去,通常通过正则表达式来进行匹配应用,决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字,含义下面会附图。
1.直接针对JoinPoint的选择
pointcuts中最常用的选择条件和Joinpoint的类型密切相关,比如图5:
《AOP之@AspectJ技术原理详解》

2.间接针对JPoint的选择
除了根据前面提到的Signature信息来匹配JPoint外,AspectJ还提供其他一些选择方法来选择JPoint。比如某个类中的所有JPoint,每一个函数执行流程中所包含的JPoint。
特别强调,不论什么选择方法,最终都是为了找到目标的JPoint。
表2列出了一些常用的非JPoint选择方法:
《AOP之@AspectJ技术原理详解》

3.匹配规则
(1)类型匹配语法
首先让我们来了解下AspectJ类型匹配的通配符:

*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。

(2)匹配模式
call(<注解?> <修饰符?> <返回值类型> <类型声明?>.<方法名>(参数列表) <异常列表>?)

  • 精确匹配
//表示匹配 com.davidkuper.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.davidkuper.MainActivity.init(Context))")
public void pointCut(){}
  • 单一模糊匹配
//表示匹配 com.davidkuper.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.davidkuper.MainActivity.*(..)) ")
public void pointCut(){}
//表示匹配调用Toast及其子类调用的show方法,不论返回类型以及参数列表,并且该子类在以com.meituan或者com.sankuai开头的包名内
@Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.meituan..*)|| within(com.sankuai..*))")
public void toastShow() {
}
  • 组合模糊匹配
//表示匹配任意Activity或者其子类的onStart方法执行,不论返回类型以及参数列表,且该类在com.meituan.hotel.roadmap包名内
@Pointcut("execution(* *..Activity+.onStart(..))&& within(com.meituan.hotel.roadmap.*)")
public void onStart(){}

(3)获取参数

  • 通过声明参数语法arg()显示获取参数
@Around(value = "execution(* BitmapFacade.picasso.init(java.lang.String,java.lang.String)) && args(arg1,arg2)"
public Object aroundArgs(String arg1,String arg2,ProceedingJoinPoint joinPoint){
   System.out.println("aspects arg = " + arg1.toString()+" " + arg2);
   Object resutObject = null;
   try {
      resutObject = joinPoint.proceed(new Object[]{arg1,arg2});
   } catch (Throwable e) {
      e.printStackTrace();
   }
   return resutObject;
}
  • 通过joinPoint.getArg()获取参数列表
@Around("execution(static * tBitmapFacade.picasso.init(..)) && !within(aspectj.*) ")
public void pointCutAround(ProceedingJoinPoint joinPoint){
   Object resutObject = null;
   try {
      //获取参数列表
      Object[] args = joinPoint.getArgs();
      resutObject = joinPoint.proceed(args);
   } catch (Throwable e) {
      e.printStackTrace();
   }
   return resutObject;
};

(4)异常匹配

/** * 截获Exception及其子类报出的异常。 * @param e 异常参数 */
@Pointcut("handler(java.lang.Exception+)&&args(e)")
public void handle(Exception e) {}

2.2.4 通知——Advise

advice是我们切面功能的实现,它是切点的真正执行的地方。比如像写日志到一个文件中,会在pointcut匹配到的连接点中插入advice(包括:before、after、around等)代码到应用程序中。
(1)@Before、@After

//所有实例方法调用截获
private static final String INSTANCE_METHOD_CALL =
"call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)";
@Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() {
}
//实例方法调用前后Advice
@Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "before instance call");
}
@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "after instance call");
}

(2)@Around

//横切项目中所有Activity的子类,以Layout命名、以及它的子类的所有方法的执行
private static final String POINTCUT_METHOD =
"(execution(* android.app.Activity+.*(..)) ||execution(* *..Layout+.*(..)))&& within(com.meituan.hotel.roadmap.*)";
@Pointcut(POINTCUT_METHOD) public void methodAnnotated() {
}

@Around("methodAnnotated()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint)throws Throwable{
   //调用原方法的执行。
   Object result = joinPoint.proceed();
   return result;
}

(3)@AfterThrowing

/** * 在异常抛出后,该操作优先于下一个切点的@Before() * @param joinPoint * @param e 异常参数 */
@AfterThrowing(pointcut = "afterThrow()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " afterThrowing() :" + e.toString());
}

2.3 执行原理

AspectJ是通过对目标工程的.class文件进行代码注入的方式将通知(Advise)插入到目标代码中。
第一步:根据pointCut切点规则匹配的joinPoint;
第二步:将Advise插入到目标JoinPoint中。
这样在程序运行时被重构的连接点将会回调Advise方法,就实现了AspectJ代码与目标代码之间的连接。
《AOP之@AspectJ技术原理详解》

JoinPoint类包UML:

《AOP之@AspectJ技术原理详解》

2.3.1 Before、After(AfterThrowing)插入示意图

Before和After的插入其实就是在匹配到的JoinPoint调用的前后插入我们编写的Before\After的Advise方法,以此来达到在目标JoinPoint执行之前先进入Advise方法,执行之后进入Advise方法。
如下图所示,目标JoinPoint为FuncB()方法,需要在他执行前后进行AOP截获:

《AOP之@AspectJ技术原理详解》

2.3.2 Around替换逻辑示意图

总体来说,使用了代理+闭包的方式进行替换,将原方法体放置到新的函数中替换,通过一个单独的闭包拆分来执行,相当于对目标JoinPoint进行了一个代理。
《AOP之@AspectJ技术原理详解》

2.3.3 代码分析

下面的Example作为目标源码,我们对它的printLog()方法进行替换、对getValue()方法调用前后插入Advise方法。

public class Example {
   String value = "value";
   public void printLog() {
     String str = getValue();
   }
   public String getValue() {
     return value;
   }
}

切面代码:

@Aspect
public class LogAspect{
//所有实例方法调用截获
private static final String INSTANCE_METHOD_CALL = "call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)"
@Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() {
}

//实例方法调用前后Advice
@Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "before instance call");
}

@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "after instance call");
}

//所有实例方法执行截获
private static final String INSTANCE_METHOD_EXECUTING = "execution(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)"
@Pointcut(INSTANCE_METHOD_EXECUTING) public void instanceMethodExecuting() {
}

//实例方法执行Advice
@Around("instanceMethodExecuting()") public Object InstanceMethodExecutingAround(ProceedingJoinPoint joinPoint){
   Log.e(getClass().getSimpleName(),
   "InstanceMethodExecuting()");
   Object result = printLog(joinPoint, "instance executing"return result;
 }
}

反编译后的结果
网上给的反编译过程都是apktool——>dex2jar——>jd­gui,这个我用hotel_road_map的debug包试过,反编译出来的jar包里面只有几个系统类,不知道什么原因,其他的包又可以正常反编译。
推荐一个反编译工具:jadx(可以直接反编译apk)。

public class Example {
private static final StaticPart ajc$tjp_0 = null;
private static final StaticPart ajc$tjp_1 = null;
private static final StaticPart ajc$tjp_2 = null;
String TAG = "Example";
String value = "value";
static {
ajc$preClinit();
}
//初始化连接点静态部分:方法名、参数列表、返回值、包路径等等。
private static void ajc$preClinit() {
Factory factory = new Factory("Example.java", Example.class);
ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_CALL, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadmap.Ex");
ajc$tjp_1 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "printLog", "com.meituan.hotel.roadm");
ajc$tjp_2 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadm");
}

//原方法的闭包,在Aspect切面中joinPoint.proceed()会调用该闭包
public class AjcClosure1 extends AroundClosure {
   public AjcClosure1(Object[] objArr) {
   super(objArr);
}

 public Object run(Object[] objArr) {
   Object[] objArr2 = this.state;
   Example.printLog_aroundBody0((Example) objArr2[0], (JoinPoint) objArr2[1]);
   return null;
 }
}
//原方法真正的方法体,在闭包中被调用
 static final void printLog_aroundBody0(Example ajc$this, JoinPoint joinPoint) {
   JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, ajc$this, ajc$thistry {
    //@Before的Advise被插入了目标代码调用之前
    LogAspect.aspectOf().beforInstanceCall(makeJP);
    String str = ajc$this.getValue();
  } finally {
   //@After的Advise被插入到目标代码调用之后,通过Finally强制执行After逻辑,每一个Before
   LogAspect.aspectOf().afterInstanceCall(makeJP);
  }
 }

  //原来的printLog()方法被AspectJ给替换了,替换成为链接AspectJ和源代码的桥梁,真正的方法体被放在了新的方法中。
  public void printLog() {
   //连接点构造
   JoinPoint makeJP = Factory.makeJP(ajc$tjp_1, this, this//将连接点与原方法的闭包连接,这样就可以在AspectJ的JoinPoint中通过joinPoint.proceed()调用闭包执行原方法。
   LogAspect.aspectOf().InstanceMethodExecutingAround(new AjcClosure1(new Object[]{this, makeJP}).linkClosureAndJoinPoint(
}

public String getValue() {
JoinPoint makeJP = Factory.makeJP(ajc$tjp_2, this, thisreturn (String)LogAspect
   .InstanceMethodExecutingAround(new AjcClosure3(new Object[]{this, makeJP})
   .linkClosureAndJoinPoint();
 }
}

Before、After(AfterThrowing)插入分析

Before\After的插入调用比较简单,通过PointCut定位匹配到JoinPoint之后,将我们编写的Before\After的切面方法直接插入到目标JoinPoint前后即可。这样就可以改变原有的代码调用轨
迹,在目标方法调用前后增加我们自己的AOP方法。

//原方法真正的方法体,在闭包中被调用
static final void printLog_aroundBody0(Example ajc$this, JoinPoint joinPoint) {
JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, ajc$this, ajc$thistry {
//@Before的Advise被插入了目标代码调用之前
LogAspect.aspectOf().beforInstanceCall(makeJP);
String str = ajc$this.getValue();
} finally {
//@After的Advise被插入到目标代码调用之后,通过Finally强制执行After逻辑,每一个Before
LogAspect.aspectOf().afterInstanceCall(makeJP);
}
}

Around替换代码分析
JoinPoint为printLog()方法,是被Around替换的,反编译后的部分代码如下:
首先,在静态初始化的时候,通过Factory会为每一个JPoint先构造出静态部分信息StaticPart。

//初始化连接点静态部分:方法名、参数列表、返回值、包路径等等。
private static void ajc$preClinit() {
   Factory factory = new Factory("Example.java", Example.class);
   ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_CALL, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadmap.Exa");
   ajc$tjp_1 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "printLog", "com.meituan.hotel.roadma");
   ajc$tjp_2 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadma");
}
public JoinPoint.StaticPart makeSJP(String kind, String modifiers, String methodName, String declaringType, String paramTypes,St
//构造方法签名实例,其中存储着方法的静态信息
Signature sig = this.makeMethodSig(modifiers, methodName, declaringType, paramTypes, paramNames, "", returnType);
return new JoinPointImpl.StaticPartImpl(count++, kind, sig, makeSourceLoc(l, ­1));
}

其次,将printLog方法体替换,以XXX_aroundBodyN(args)命名,原方法体被替换如下:

//原来的printLog()方法被AspectJ给替换了,替换成为链接AspectJ和源代码的桥梁,真正的方法体被放在了新的方法中。
public void printLog() {
//连接点构造
JoinPoint makeJP = Factory.makeJP(ajc$tjp_1, this, this//将连接点与原方法的闭包连接,这样就可以在AspectJ的JoinPoint中通过joinPoint.proceed()调用闭包执行原方法。
LogAspect.aspectOf().InstanceMethodExecutingAround(new AjcClosure1(new Object[]{this, makeJP}).linkClosureAndJoinPoint(
}

AroundClosure闭包,将运行时对象和当前连接点JP对象传入其中,调用linkClosureAndJoinPoint()进行两端的绑定,这样在Around中就可以通过ProceedingJoinPoint.proceed()调用AroundClosure,进而调用目标方法。

public abstract class AroundClosure {
   protected Object[] state;
   protected Object[] preInitializationState;
   public AroundClosure() {
   }
   public AroundClosure(Object[] state) {
      this.state = state;
   }

   public ProceedingJoinPoint linkClosureAndJoinPoint() {
     //获取执行链接点,默认数组最后一个是连接点
     ProceedingJoinPoint jp = 
     (ProceedingJoinPoint)state[state.length­1//设置执行时闭包
     jp.set$AroundClosure(thisreturn jp;
   }
}

JoinPointImpl,包括一个JoinPoint的静态部分和实例部分:

class JoinPointImpl implements ProceedingJoinPoint {
   //JP静态部分
   static class StaticPartImpl implements JoinPoint.StaticPart {
      String kind;
      Signature signature;
      SourceLocation sourceLocation;
      private int id;
      //省略
   }
   Object _this;
   Object target;
   Object[] args;
   org.aspectj.lang.JoinPoint.StaticPart staticPart;
   //省略....
   // To proceed we need a closure to proceed on
   private AroundClosure arc;
   public void set$AroundClosure(AroundClosure arc) {
      this.arc = arc;
   }
   //通过proceed()调用闭包arc的run方法,并且传入JP的执行状态:参数列表等。进而调用原方法体执行
   public Object proceed() throws Throwable {
     //when called from a before advice, but be a no­op
     if (arc == null)
        return null;
     else
        return arc.run(arc.getState());
    }
    //省略....
}

2.4 AspectJ切面编写

2.4.1 日志打印

(1)追踪某些特定方法的调用日志,统计调用的频率
(2)关注某类方法的日志
(3)全局日志的打印
AOP示例代码:

/** * Created by malingyi on 2017/3/22. */
/** * 日志打印。(分静态调用、静态执行、实例调用、实例执行四类日志) */
@Aspect public class LogAspect {
//所有静态方法调用截获
private static final String STATIC_METHOD_CALL =
"call(static * com.meituan.hotel.roadmap..*.*(..))";
@Pointcut(STATIC_METHOD_CALL) public void staticMethodCutting() {
}
@Before("staticMethodCutting()") public void beforStaticCall(JoinPoint joinPoint) {
printLog(joinPoint, "before static call");
}
@After("staticMethodCutting()") public void afterStaticCall(JoinPoint joinPoint) {
printLog(joinPoint, "after static call");
}
//所有实例方法调用截获
private static final String INSTANCE_METHOD_CALL =
"call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)";
@Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() {
}
//实例方法调用前后Advice
@Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "before instance call");
}
@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "after instance call");
}
//所有静态方法执行截获
private static final String STATIC_METHOD_EXECUTING =
"execution(static * com.meituan.hotel.roadmap..*.*(..)) && !within(com.example.monitor.*)";
@Pointcut(STATIC_METHOD_EXECUTING) public void staticExecutionCutting() {
}
//所有实例方法执行截获
private static final String INSTANCE_METHOD_EXECUTING =
"execution(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)&& !within(com.example.monitor.*)";
@Pointcut(INSTANCE_METHOD_EXECUTING) public void instanceMethodExecuting() {
}
//静态方法执行Advice
@Around("staticExecutionCutting()") public Object staticMethodExecuting(
ProceedingJoinPoint joinPoint) {
Log.e(getClass().getSimpleName(), "staticMethodExecuting()");
Object result = printLog(joinPoint, "static executing"return result;
}
//实例方法执行Advice
@Around("instanceMethodExecuting()") public Object InstanceMethodExecuting(
ProceedingJoinPoint joinPoint) {
Log.e(getClass().getSimpleName(), "InstanceMethodExecuting()");
Object result = printLog(joinPoint, "instance executing"return result;
}
/** * 日志打印和统计 * @param joinPoint * @param describe * @return */
private Object printLog(JoinPoint joinPoint, String describe) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
try {
if (joinPoint instanceof ProceedingJoinPoint) {
return ((ProceedingJoinPoint) joinPoint).proceed(joinPoint.getArgs());
}
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
Log.e(getClass().getSimpleName(), describe + " : " + signature.toLongString());
}
return null;
}
}

结果:

04­24 02:39:51.388 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.388 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public int
com.meituan.hotel.roadmap.MainActivity.ViewPagerFragmentAdapter.getCount()
04­24 02:39:51.418 3081­3171/com.meituan.hotel.roadmap E/Surface: getSlotFromBufferLocked: unknown buffer: 0xf2ca76e0
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public void
com.meituan.hotel.roadmap.RoadMapApplication.2.onActivityStopped(android.app.Activity)
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : protected void com.meituan.hotel.roadmap.BaseActivity.onStop()
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/ContentValues: HotelDetailActivity 停留时间: 4657.063 ms
04­24 02:39:51.668 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.668 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public void
com.meituan.hotel.roadmap.RoadMapApplication.2.onActivityDestroyed(android.app.Activity)

2.4.2 耗时监控

示例代码1:
步骤:
1、编写AspectJ的语法,横切需要关注的切点
2、在其执行前后增加计时器
3、输出时间日志,进行统计分析

/** * 时间监控 */
@Aspect public class TimeMonitorAspect {
//横切项目中所有Activity的子类,以Layout命名、以及它的子类的所有方法的执行
private static final String POINTCUT_METHOD =
"(execution(* android.app.Activity+.*(..)) ||execution(* *..Layout+.*(..)))&& within(com.meituan.hotel.roadmap.*)"
@Pointcut(POINTCUT_METHOD) public void methodAnnotated() {
}
/** * 截获原方法的执行,添加计时器,监控单个方法的耗时 * @throws Throwable */
@Around("methodAnnotated()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint)
throws Throwable {
//初始化计时器
final StopWatch stopWatch = new StopWatch();
//开始监听
stopWatch.start();
//调用原方法的执行。
Object result = joinPoint.proceed();
//监听结束
stopWatch.stop();
//日志打印
printLog(joinPoint, stopWatch);
return result;
}
private void printLog(JoinPoint joinPoint, StopWatch stopWatch) {
//获取方法信息对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className;
//获取当前对象,通过反射获取类别详细信息
className = joinPoint.getThis().getClass().getName();
String methodName = methodSignature.getName();
String msg = buildLogMessage(methodName, stopWatch.getTotalTime(1));
//日志存储、打印
TimeMonitorLog.log(new MethodMsg(className, msg, (long) stopWatch.getTotalTime(1)));
// TimeMonitorLog.writeToSDCard(new Path()); //日志存储
// TimeMonitorLog.ReadIn(new Path()); //日志读取
}

/** * 创建一个日志信息 * @param methodName 方法名 * @param methodDuration 执行时间 */
private static String buildLogMessage(String methodName, double 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("] \n"return message.toString();
}
}

结果:

03­27 04:31:35.681 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onStop ­­> [0.286ms]
03­27 04:31:39.040 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.HotelDetailActivity getLayoutId ­­> [0.003ms]
03­27 04:31:39.047 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onCreate ­­> [7.217ms]
03­27 04:31:39.048 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.HotelDetailActivity onCreate ­­> [7.972ms]
03­27 04:31:39.050 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onStart ­­> [0.276ms]

示例代码2——监控Activity页面的停留时间
步骤:
1、编写横切项目中Activity的onStart()、onStop()的切点语法
2、然后在onStart()切点执行之前启动计时器,将其与该页面对象存入Map中进行绑定
3、在onStop()切点执行完毕之后,从通过该对象从Map中获取计时器,然后结束计时,输出日志。

/** * 时间监控 */
@Aspect public class TimeMonitorAspect {
private static final String TAG = "TimeMonitorAspect";
//存放<页面对象,计时器>
private HashMap<Object,StopWatch> map = new HashMap<>();
/** * 横切页面的onStart和onStop方法,监控两个方法之间的耗时 */
@Pointcut("execution(* *..Activity+.onStart(..))&&this(java.lang.Object)&& within(com.meituan.hotel.roadmap.*)")
public void onStart(){}
@Pointcut("execution(* *..Activity+.onStop(..))&&this(java.lang.Object)&& within(com.meituan.hotel.roadmap.*)")
public void onStop(){}
@Pointcut("onStart() && !cflowbelow(onStart())")
public void realOnStart(){}
@Pointcut("onStop() && !cflowbelow(onStop())")
public void realOnStop(){}
/** * 在onCreate()调用时,开启该页面的计时器,将计时器存入HashMap<Object,StopWatch>中。 * @param joinPoint * @return */
@Around("realOnStart()")
public Object AroundOnStart(ProceedingJoinPoint joinPoint){
Object result = null;
Object target = joinPoint.getTarget();
StopWatch stopWatch = new StopWatch();
if (target != null){
map.put(target,stopWatch);
}
try {
stopWatch.start();
result = joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
/** * 在onStop()结束时,从HashMap<Object,StopWatch>中获取该计时器,停止该页面的计时器,并将时间打印出来。 * @param joinPoint * @return */
@Around("realOnStop()")
public Object AroundOnStop(ProceedingJoinPoint joinPoint){
Object result = null;
Object target = joinPoint.getTarget();
StopWatch stopWatch = null;
if (target != null){
stopWatch = map.get(target);
}
try {
result = joinPoint.proceed(joinPoint.getArgs());
if (stopWatch != null){
stopWatch.stop();
//打印日志
Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " 停留时间: "+ stopWatch.getTotalTimeMillis() + " ms"
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}

结果:

03­27 05:01:16.629 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: HotelDetailActivity 停留时间: 4170.12 ms
03­27 04:31:39.465 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: MainActivity 停留时间: 4112.293 ms

2.4.3 异常处理

示例代码1——截获谋类异常

/** * 异常处理 */
@Aspect
public class ExceptionHandleAspect {
private static final String TAG = "ExceptionHandleAspect";
/** * 截获空指针异常 * @param e */
@Pointcut("handler(java.lang.NullPointerException)&&args(e)")
public void handle(NullPointerException e){
}
/** * 在catch代码执行之前做一些处理 * @param joinPoint * @param e 异常参数 */
@Before(value = "handle(e)",argNames = "e")
public void handleBefore(JoinPoint joinPoint,NullPointerException e){
Log.e(TAG,joinPoint.getSignature().toLongString()+" handleBefore() :"+e.toString());
//汇总处理
}
}

结果:

03­27 06:46:48.700 11618­11618/com.meituan.hotel.roadmap E/ExceptionHandleAspect: MainActivity afterThrowing() :java.lang.NullPointerException: null

项目中所有的NullPointerException的catch()方法执行之前都会被截获。

示例代码2——截获指定方法的异常

/** * 异常处理 */
@Aspect
public class ExceptionHandleAspect {
private static final String TAG = "ExceptionHandleAspect";
/** * 截获某一个方法抛出的异常 */
@Pointcut("call(* com.meituan.hotel.roadmap.*.initTabLayout(..))")
public void afterThrow(){}
/** * 在异常抛出后,该操作优先于下一个切点的@Before() * @param joinPoint * @param e 异常参数 */
@AfterThrowing(pointcut = "afterThrow()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " afterThrowing() :" + e.toString());
}
}

结果:

03­27 06:46:48.700 11618­11618/com.meituan.hotel.roadmap E/ExceptionHandleAspect: MainActivity afterThrowing() :java.lang.NullPointerException: null

在MainActivity上调用initTabLayout()方法时抛出一个空指针,抛出后被截获了。

2.4.4 降级替代方案——吐司

在Android 5.0以后,有些手机关闭通知权限会导致Toast通知无法显示。有些项目在这之前就已经有很多Toast的使用了,那么这个时候就需要在Toast代码前后做权限验证,然后再使用备用方法替代。

使用AOP就可以将这种重复的代码聚焦在一处进行处理。类似于这样的,在代码工程迭代过程中,会大量重复用到的降级替代都可以使用AOP的思想。
步骤:
1、自定义一个Toast的View,该View不依赖于Notification通知权限。
2、截获项目中调用Toast的.show()方法。

@Aspect
public class ToastAspect {
private static final String TAG = "toastAspect";
@Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.meituan..*)|| within(com.sankuai..*))")
public void toastShow() {
}
@Pointcut("toastShow() && !cflowbelow(toastShow())")
public void realToastShow() {
}
@Around("realToastShow()")
public void toastShow(ProceedingJoinPoint point) {
try {
Toast toast = (Toast) point.getTarget();
Context context = (Context) getValue(toast, "mContext"//如果当前没有context意味着可能页面被回收,或者的版本在19以上且通知可用,执行系统的Toast方案交给系统处理
if (context == null || Build.VERSION.SDK_INT >= 19 && NotificationManagerCompat.from(context).areNotificationsEnabl
//use system function
point.proceed(point.getArgs());
} else {//如果context存在,并且通知不可用,则使用自定义的Toast
//Toast params
int mDuration = toast.getDuration();
View mNextView = toast.getView();
int mGravity = toast.getGravity();
int mX = toast.getXOffset();
int mY = toast.getYOffset();
float mHorizontalMargin = toast.getHorizontalMargin();
float mVerticalMargin = toast.getVerticalMargin();
new MToast(context instanceof Application ? context : context.getApplicationContext())
.setDuration(mDuration)
.setView(mNextView)
.setGravity(mGravity, mX, mY)
.setMargin(mHorizontalMargin, mVerticalMargin).show();
}
} catch (Throwable exception) {
//ignore
}
}
// TODO: 2016/12/14 toast.cancel() can't be work with MToast
/** * 通过字段名从对象或对象的父类中得到字段的值 * * @param object 对象实例 * @param fieldName 字段名 * @return 字段对应的值 * @throws Exception */
public static Object getValue(Object object, String fieldName) throws Exception {
if (object == null || TextUtils.isEmpty(fieldName)) {
return null;
}
Field field = null;
Class<?> clazz = object.getClass();
for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(truereturn field.get(object);
} catch (Exception e) {
//ignore
}
}
return null;
}
}

2.4.5 其他的系统横切关注点问题

使用特点:

  • 关注点具有普遍性需求,代码散乱分布在工程各处,可以抽出共同的代码。
  • 访问控制,例如:字段、方法的访问前做一些验证,访问之后做一些处理。
  • 代码约束,例如:限制某些方法只能在特定的地方使用,否则在编译期间抛出Error错误或者Warning。
  • 项目中需要临时插入一些方法、逻辑,但是不希望影响到原工程,易插易拔。

三、相关问题

3.1 编织速度

(1)尽量使用精确的匹配规则,降低匹配时间。
(2)排除不需要扫描的包。

// AspectJ
aspectj {
disableWhenDebug true
javartNeeded true
// 支付方排除
exclude group: 'com.sankuai.pay', module: 'buymodel'
exclude group: 'com.meituan.android.cashier', module: 'library'
// 第三方异常包排除
exclude group: 'com.dianping.nova.common', module: 'push'
exclude group: 'org.freemarker', module: 'freemarker'
compileOptions {
defaultJavaVersion = JavaVersion.VERSION_1_7
}
}

(3)硬件配置升级。

3.2 调试工具

Eclipse和Intellij 12支持AJDT调试工具,但是目前AndroidStudio并不支持,只能在Gradle构建时查看日志。
(1)切点:
《AOP之@AspectJ技术原理详解》
(2)目标代码:
《AOP之@AspectJ技术原理详解》

Gradle构建日志:
《AOP之@AspectJ技术原理详解》

    原文作者:AOP
    原文地址: https://blog.csdn.net/woshimalingyi/article/details/73252013
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞