AOP之AspectJ 技术原理详解及实战总结

2024-03-06 16:40

本文主要是介绍AOP之AspectJ 技术原理详解及实战总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 一AOP
    • 1 主要功能
    • 2 主要目标
    • 3 适用对象
    • 4 AOP与OOP的关系
  • 二Android中使用AspectJ
    • 1 Gradle 配置示
    • 2 基本概念
      • 21 切面Aspect
      • 22 连接点JoinPoint
      • 23 切点PointCut
      • 24 通知Advise
    • 3 执原
      • 31 BeforeAfterAfterThrowing插入示意图
      • 32 Around替换逻辑示意图
      • 33 代码分析
    • 4 AspectJ切面编写
      • 41 日志打印
      • 42 耗时监控
      • 43 异常处
      • 44 降级替代方案吐司
      • 45 其他的系统横切关注点问题
  • 三相关问题
    • 1 编织速度
    • 2 调试工具

一、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我们的调试和监控就变得简单清晰。它们之间的关系如下图所示:

这里写图片描述

1.4.1 对比一——单一横切关注点
这里写图片描述

这里写图片描述

1.4.2 对比二——多横切关注点
这里写图片描述

结论:
这里写图片描述


二、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.javaCompilejavaCompile.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(true);new Main().run(args, handler)def log = project.loggerfor (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}
}
}

附:美团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的:
这里写图片描述

2.2.3 切点——PointCut

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

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

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;
};
  • 12

(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代码与目标代码之间的连接。
这里写图片描述

JoinPoint类包UML:

这里写图片描述

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

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

这里写图片描述

2.3.2 Around替换逻辑示意图

总体来说,使用了代理+闭包的方式进行替换,将原方法体放置到新的函数中替换,通过一个单独的闭包拆分来执行,相当于对目标JoinPoint进行了一个代理。
这里写图片描述

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$this);try {//@Before的Advise被插入了目标代码调用之前LogAspect.aspectOf().beforInstanceCall(makeJP);String str = ajc$this.getValue();} finally {//@After的Advise被插入到目标代码调用之后,通过Finally强制执行After逻辑,每一个BeforeLogAspect.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, this);return (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$this);
try {
//@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(this);return 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 onprivate 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­opif (arc == null)return null;elsereturn 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(true);
return 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)切点:
这里写图片描述
(2)目标代码:
这里写图片描述

Gradle构建日志:
这里写图片描述

有技术困惑的小伙伴可以加群交流

 

群号:168786059

加群备注:技术交流

 

这篇关于AOP之AspectJ 技术原理详解及实战总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/780692

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

乐鑫 Matter 技术体验日|快速落地 Matter 产品,引领智能家居生态新发展

随着 Matter 协议的推广和普及,智能家居行业正迎来新的发展机遇,众多厂商纷纷投身于 Matter 产品的研发与验证。然而,开发者普遍面临技术门槛高、认证流程繁琐、生产管理复杂等诸多挑战。  乐鑫信息科技 (688018.SH) 凭借深厚的研发实力与行业洞察力,推出了全面的 Matter 解决方案,包含基于乐鑫 SoC 的 Matter 硬件平台、基于开源 ESP-Matter SDK 的一

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

持久层 技术选型如何决策?JPA,Hibernate,ibatis(mybatis)

转自:http://t.51jdy.cn/thread-259-1-1.html 持久层 是一个项目 后台 最重要的部分。他直接 决定了 数据读写的性能,业务编写的复杂度,数据结构(对象结构)等问题。 因此 架构师在考虑 使用那个持久层框架的时候 要考虑清楚。 选择的 标准: 1,项目的场景。 2,团队的技能掌握情况。 3,开发周期(开发效率)。 传统的 业务系统,通常业

19.手写Spring AOP

1.Spring AOP顶层设计 2.Spring AOP执行流程 下面是代码实现 3.在 application.properties中增加如下自定义配置: #托管的类扫描包路径#scanPackage=com.gupaoedu.vip.demotemplateRoot=layouts#切面表达式expression#pointCut=public .* com.gupaoedu

十五.各设计模式总结与对比

1.各设计模式总结与对比 1.1.课程目标 1、 简要分析GoF 23种设计模式和设计原则,做整体认知。 2、 剖析Spirng的编程思想,启发思维,为之后深入学习Spring做铺垫。 3、 了解各设计模式之间的关联,解决设计模式混淆的问题。 1.2.内容定位 1、 掌握设计模式的"道" ,而不只是"术" 2、 道可道非常道,滴水石穿非一日之功,做好长期修炼的准备。 3、 不要为了

十四、观察者模式与访问者模式详解

21.观察者模式 21.1.课程目标 1、 掌握观察者模式和访问者模式的应用场景。 2、 掌握观察者模式在具体业务场景中的应用。 3、 了解访问者模式的双分派。 4、 观察者模式和访问者模式的优、缺点。 21.2.内容定位 1、 有 Swing开发经验的人群更容易理解观察者模式。 2、 访问者模式被称为最复杂的设计模式。 21.3.观察者模式 观 察 者 模 式 ( Obser

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

亮相WOT全球技术创新大会,揭秘火山引擎边缘容器技术在泛CDN场景的应用与实践

2024年6月21日-22日,51CTO“WOT全球技术创新大会2024”在北京举办。火山引擎边缘计算架构师李志明受邀参与,以“边缘容器技术在泛CDN场景的应用和实践”为主题,与多位行业资深专家,共同探讨泛CDN行业技术架构以及云原生与边缘计算的发展和展望。 火山引擎边缘计算架构师李志明表示:为更好地解决传统泛CDN类业务运行中的问题,火山引擎边缘容器团队参考行业做法,结合实践经验,打造火山

React+TS前台项目实战(十七)-- 全局常用组件Dropdown封装

文章目录 前言Dropdown组件1. 功能分析2. 代码+详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局Dropdown组件封装,可根据UI设计师要求自定义修改。 Dropdown组件 1. 功能分析 (1)通过position属性,可以控制下拉选项的位置 (2)通过传入width属性, 可以自定义下拉选项的宽度 (3)通过传入classN