• @Aspect 用它声明一个类,表示一个需要执行的切面。
  • @Pointcut 声明一个切点。
  • @Before/@After/@Around/…(统称为Advice类型) 声明在切点前、后、中执行切面代码。

这么说你可能有点蒙,我们换个角度解释。

假设你是一个AOP框架的设计者,最先需要理清的其基本组成要素。既然需要做代码织入那是不是一定得配置代码的织入点呢?这个织入点就是Pointcut,有了织入点我们还需要指定具体织入的代码,这个代码写在哪里呢?就是写在以@Before/@After/@Around注解的方法体内。有了织入点和织入代码,还需要告诉框架自己是一个面向切面的配置文件,这就需要使用@Aspect声明在类上。

我们举个简单的栗子,全部示例参考github [sample_aspectj](()。

@Aspect //①
public class MethodAspect {

@Pointcut(“call(* com.wandering.sample.aspectj.Animal.fly(…))”)//②
public void callMethod() {
}

@Before(“callMethod()”)//③
public void beforeMethodCall(JoinPoint joinPoint) {
Log.e(TAG, “before->” + joinPoint.getTarget().toString()); //④
}
}

我们事先准备好的Animal类中有一个fly方法。

public class Animal {
public void fly() {
Log.e(TAG, “animal fly method:” + this.toString() + “#fly”);
}
}

①处声明了本类是一个AspectJ配置文件。

②处指定了一个代码织入点,注解内的call(* com.wandering.sample.aspectj.Animal.fly(…)) 是一个切点表达式,第一个*号表示返回值可为任意类型,后跟包名+类名+方法名,括号内表示参数列表, 表示匹配任意个参数,参数类型为任何类型,这个表达式指定了一个时机:在Animal类的fly方法被调用时。

③处声明Advice类型为Before并指定切点为上面callMethod方法所表示的那个切点。

④处为实际织入的代码。

翻译成白话就是说在Animal类的fly方法被调用前插入④处的代码。

编写测试代码并调用fly方法,运行观察日志输出你会发现before->的日志先于animal fly日志被打印,具体可查看sample工程MethodAspect示例。

我们再将APK反编译看一下织入结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qju0X2dp-1651545443167)(https://user-gold-cdn.xitu.io/2019/12/27/16f45564fa1108a4?imageView2/0/w/1280/h/960/ignore-error/1)]

红色框选部分就是AspectJ为我们织入的代码。

通过上面的例子我们了解了AspectJ的基本用法,但实际上AspectJ的语法可以十分复杂,下面我们来看看具体的语法。

Join Point

上面的例子中少讲了一个连接点的概念,连接点表示可织入代码的点,它属于Pointcut的一部分。由于语法内容较多,实际使用过程中我们可以参考[语法手册]((),我们列出其中一部分Join Point:

Joint Point 含义
Method call 方法被调用
Method execution 方法执行
Constructor call 构造函数被调用
Constructor execution 构造函数执行
Static initialization static 块初始化
Field get 读取属性
Field set 写入属性
Handler 异常处理

Method call 和 Method execution的区别常拿来比较,其实就是调用与执行的区别,就拿上面Animal的fly方法举例。demo代码如下:

Animal a = Animal();
a.fly();

如果我们声明的织入点为call,再假设Advice类型是before,则织入后代码结构是这样的。

Animal a = new Animal();
//…我是织入代码
a.fly();

如果我们声明的织入点为execution,则织入后代码结构就成这样了。

public class Animal {
public void fly() {
//…我是织入代码
Log.e(TAG, “animal fly method:” + this.toString() + “#fly”);
}
}

本质上的区别就是织入对象不同,call被织入在指定方法被调用的位置上,而execution被织入到指定的方法内部。

Pointcut

Pointcuts是具体的切入点,基本上Pointcuts 是和 Join Point 相对应的。

Joint Point Pointcuts 表达式
Method call call(MethodPattern)
Method execution execution(MethodPattern)
Constructor call call(ConstructorPattern)
Constructor execution execution(ConstructorPattern)
Static initialization staticinitialization(TypePattern)
Field get get(FieldPattern)
Field set set(FieldPattern)
Handler handler(TypePattern)

除了上面与 Join Point 对应的选择外,Pointcuts 还有其他选择方法。

Pointcuts 表达式 说明
within(TypePattern) 符合 TypePattern 的代码中的 Join Point
withincode(MethodPattern) 在某些方法中的 Join Point
withincode(ConstructorPattern) 在某些构造函数中的 Join Point
cflow(Pointcut) Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,包括 P 本身
cflowbelow(Pointcut) Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,不包括 P 本身
this(Type or Id) Join Point 所属的 this 对象是否 instanceOf Type 或者 Id 的类型
target(Type or Id) Join Point 所在的对象(例如 call 或 execution 操作符应用的对象)是否 instanceOf Type 或者 Id 的类型
args(Type or Id, …) 方法或构造函数参数的类型
if(BooleanExpression) 满足表达式的 Join Point,表达式只能使用静态属性、Pointcuts 或 Advice 暴露的参数、thisJoinPoint 对象

this vs. target

this和target是一个容易混淆的点。

MethodAspect.java

public class MethodAspect {
@Pointcut(“call(* com.wandering.sample.aspectj.Animal.fly(…))”)
public void callMethod() {
Log.e(TAG, “callMethod->”);
}

@Before(“callMethod()”)
public void beforeMethodCall(JoinPoint joinPoint) {
Log.e(TAG, “getTarget->” + joinPoint.getTarget());
Log.e(TAG, “getThis->” + joinPoint.getThis());
}
}

fly调用方:

MainActivity.java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Animal animal = new Animal();
animal.fly();
}

运行结果如下:

getTarget->com.wandering.sample.aspectj.Animal@509ddfd
getThis->com.wandering.sample.aspectj.MainActivity@98c38bf

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

我们稍加改动,将切点的call改为execution。

运行结果就成这个样子了:

getTarget->com.wandering.sample.aspectj.Animal@509ddfd
getThis->com.wandering.sample.aspectj.Animal@509ddfd

按照上面的分析,与这个结果也是吻合的。

条件运算

Pointcut表达式中还可以使用一些条件判断符,比如 !、&&、||。

以Hugo为例:

Hugo.java

@Pointcut(“within(@hugo.weaving.DebugLog *)”)
public void withinAnnotatedClass() {}

@Pointcut(“execution(!synthetic * *(…)) && withinAnnotatedClass()”)
public void methodInsideAnnotatedType() {}

第一个切点指定范围为包含DebugLog注解的任意类和方法,第二个切点为在第一个切点范围内,且执行非内部类的任意方法。结合起来表述就是任意声明了DebugLog注解的方法。

其中@hugo.weaving.DebugLog *!synthetic * *(..)分别对应上面表格中提到的TypePattern和MethodPattern。

接下来需要了解这些pattern具体的语法,通过语法我们可以写出符合自身需求的表达式。

Pattern类型 语法
MethodPattern [!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型]
ConstructorPattern [!] [@Annotation] [public,protected,private] [final] [类名.]new(参数类型列表) [throws 异常类型]
FieldPattern [!] [@Annotation] [public,protected,private] [static] [final] 属性类型 [类名.]属性名
TypePattern 其他 Pattern 涉及到的类型规则也是一样,可以使用 ‘!’、‘’、‘…’、‘+’,‘!’ 表示取反,‘’ 匹配除 . 外的所有字符串,‘*’ 单独使用事表示匹配任意类型,‘…’ 匹配任意字符串,‘…’ 单独使用时表示匹配任意长度任意类型,‘+’ 匹配其自身及子类,还有一个 '…'表示不定个数

更多语法参见官网[Pointcuts]((),非常有用。

再看几个例子:

execution(void setUserVisibleHint(…)) && target(android.support.v4.app.Fragment) && args(boolean) — 执行 Fragment 及其子类的 setUserVisibleHint(boolean) 方法时。

execution(void Foo.foo(…)) && cflowbelow(execution(void Foo.foo(…))) — 执行 Foo.foo() 方法中再递归执行 Foo.foo() 时。

if条件

通常情况下,Pointcuts注解的方法参数列表为空,返回值为void,方法体也为空。但是如果表达式中声明了:

  • args、target、this等类型参数,则可额外声明参数列表。
  • if条件,则方法必须public static boolean

来看sample示例MethodAspect8:

@Aspect
public class MethodAspect8 {
@Pointcut(“call(boolean .(int)) && args(i) && if()”)
public static boolean someCallWithIfTest(int i, JoinPoint jp) {
// any legal Java expression…
return i > 0 && jp.getSignature().getName().startsWith(“setAge”);
}

@Before(“someCallWithIfTest(i, jp)”)
public void aroundMethodCall(int i, JoinPoint jp) {
Log.e(TAG, "before if ");
}

}

切点方法 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 someCallWithIfTest声明的注解表示任意方法,此方法返回值为boolean,参数签名为仅一个int类型的参数,后面跟上if条件,表示此int参数值大于0,且方法签名以setAge开头。

如此一来切面代码的执行就具备了动态性,但不是说不满足if条件的切点就不会织入代码。依然会织入,只是在调用织入代码前会执行someCallWithIfTest方法,当返回值为true时才会执行织入代码,下图是反编译class的结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-esTc1Np7-1651545443168)(https://user-gold-cdn.xitu.io/2019/12/27/16f455650c61926f?imageView2/0/w/1280/h/960/ignore-error/1)]

了解了原理后,实际上if逻辑也完全可以放到织入点代码中,理解起来会更容易一些。

Advice

直译过来是通知,实际上表示一类代码织入位置,在AspectJ中有五种类型的注解:Before、After、AfterReturning、AfterThrowing、Around,我们将它们统称为Advice注解。

Advice 说明
@Before 切入点前织入
@After 切入点后织入,无论连接点执行如何,包括正常的 return 和 throw 异常
@AfterReturning 只有在切入点正常返回之后才会执行,不指定返回类型时匹配所有类型
@AfterThrowing 只有在切入点抛出异常后才执行,不指定异常类型时匹配所有类型
@Around 替代原有切点,如果要执行原来代码的话,调用 ProceedingJoinPoint.proceed()

Advice注解修饰的方法有一些约束:

  1. 方法必须为public。
  2. Before、After、AfterReturning、AfterThrowing 四种类型方法返回值必须为void。
  3. Around的目标是替代原切入点,它一般会有返回值,这就要求声明的返回值类型必须与切入点方法的返回值保持一致;不能和其他 Advice 一起使用,如果在对一个 Pointcut 声明 Around 之后还声明 Before 或者 After 则会失效
  4. 方法签名可以额外声明JoinPoint、JoinPointStaticPart、JoinPoint.EnclosingStaticPart。

JoinPoint、JoinPointStaticPart、JoinPoint.EnclosingStaticPart又是什么呢?

在执行切面代码时,AspectJ会将连接点处的上下文信息封装成JoinPoint供我们使用。这些信息中有些是在编译阶段就可以确定的,比如方法签名 joinPoint.getSignature(),JoinPoint类型 joinPoint.getKind(),切点代码位置类名+行数joinPoint.getSourceLocation() 等等,我们将他们统称为JoinPointStaticPart。

而还有一些是在运行时才能确定的,比如前文提到的this、target、实参等等。

  • JoinPoint 包含连接点处的静态信息+动态信息。
  • JoinPointStaticPart 连接点处的静态信息。
  • EnclosingStaticPart 包含了连接点的静态信息,也就是连接点的上下文。

如果不需要动态信息,建议使用静态类型的参数,以提高性能。

讲了这么多理论,看起来比较复杂,实际上我们日常开发中的场景要相对简单一些。

常用示例

  • 等等,我们将他们统称为JoinPointStaticPart。

而还有一些是在运行时才能确定的,比如前文提到的this、target、实参等等。

  • JoinPoint 包含连接点处的静态信息+动态信息。
  • JoinPointStaticPart 连接点处的静态信息。
  • EnclosingStaticPart 包含了连接点的静态信息,也就是连接点的上下文。

如果不需要动态信息,建议使用静态类型的参数,以提高性能。

讲了这么多理论,看起来比较复杂,实际上我们日常开发中的场景要相对简单一些。

常用示例

Android AspectJ详解相关推荐

  1. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  2. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  3. Android LayoutInflater详解

    Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...

  4. android Fragments详解

    android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...

  5. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  6. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

  7. android子视图无菜单,Android 菜单详解

    Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...

  8. Android StateFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121913352 本文出自[赵彦军的博客] 文章目录 系列文章 一.冷流还是热流 S ...

  9. Android SharedFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121911675 本文出自[赵彦军的博客] 文章目录 系列文章 什么是SharedF ...

最新文章

  1. 黄聪:穿过主机访问虚拟机中的SQL服务 FOR VMware NAT
  2. 原来被原子弹炸到是这种感觉!也太刺激了吧!
  3. 怎么把加载图标去掉_怎样在PCB上绘制图标
  4. JFoenix: JavaFX与Google Material Design
  5. 从0到1,马蜂窝大交通团队如何构建高效研发流程体系?
  6. c++ int8_t转int_Python 90行代码让微信地球转起来,你也可以!| 原力计划
  7. [BZOJ1419] Red is good(期望DP)
  8. Spark Windows
  9. Memcached和Redis
  10. [NVIDIA] Ubuntu 卸载 cuda
  11. python学习的第十八天模块之包、相对搜索路径和绝对搜索路径
  12. hp1015驱动64位_惠普1015打印机驱动下载
  13. IPMI IPMB协议
  14. 百度文库的所有内容都可以不用财富值下载
  15. 华创e路航固件_华创e路航地图升级工具 v1.0 官方版(图文)
  16. wsl2安装及一些使用技巧
  17. Duilib的界面设计工具DuiDesigner的使用说明
  18. 【已解决】AndroidStudio不显示控件解决方案
  19. android 让app全屏显示,Android app设置全屏模式
  20. 北京冬奥村:让科技蕴含温度

热门文章

  1. 关于《最强大脑》周玮的一些想法
  2. sicily 1048 Inverso
  3. 从奶茶恋看刘强东马云气质之不同
  4. 【个人博客网站seo】小白站长一分钟了解新站seo
  5. kindle可以设置24小时吗_Kindle | 怎样安排时间读书?
  6. C# 解决连接Oracle数据库提示“需要Oracle客户端软件version8.1.7或更高版本”的问题
  7. 华为鸿蒙系统下载猫薄荷,华为鸿蒙系统官网下载_华为鸿蒙系统官网2.0系统安装包免费分享 v1.0-安族软件网...
  8. 最老程序员创业札记:全文检索、数据挖掘、推荐引擎应用7
  9. 选购wordpress主机创建自己的博客
  10. 智能导诊——融威众邦