本文讨论一下Spring AOP编程中的两个关键问题,定义切点和定义通知,理解这两个问题能应付大部分AOP场景。

如果你还不熟悉AOP,请先看AOP基本原理,本文的例子也沿用了AOP基本原理中的例子。

切点表达式

切点的功能是指出切面的通知应该从哪里织入应用的执行流。切面只能织入公共方法。

在Spring AOP中,使用AspectJ的切点表达式语言定义切点其中excecution()是最重要的描述符,其它描述符用于辅助excecution()

excecution()的语法如下

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

这个语法看似复杂,但是我们逐个分解一下,其实就是描述了一个方法的特征:

问号表示可选项,即可以不指定。

excecution(* com.tianmaying.service.BlogService.updateBlog(..))

  • modifier-pattern:表示方法的修饰符
  • ret-type-pattern:表示方法的返回值
  • declaring-type-pattern?:表示方法所在的类的路径
  • name-pattern:表示方法名
  • param-pattern:表示方法的参数
  • throws-pattern:表示方法抛出的异常

注意事项

  • 其中后面跟着“?”的是可选项。
  • 在各个pattern中,可以使用"*"来表示匹配所有。
  • 在param-pattern中,可以指定具体的参数类型,多个参数间用“,”隔开,各个也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String)表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。
  • 可以用(..)表示零个或多个任意的方法参数。

使用&&符号表示与关系,使用||表示或关系、使用!表示非关系。在XML文件中使用andornot这三个符号。

在切点中引用Bean

Spring还提供了一个bean()描述符,用于在切点表达式中引用Spring Beans。例如:

excecution(* com.tianmaying.service.BlogService.updateBlog(..))  and bean('tianmayingBlog')

这表示将切面应用于BlogServiceupdateBlog方法上,但是仅限于ID为tianmayingBlog的Bean。

也可以排除特定的Bean:

excecution(* com.tianmaying.service.BlogService.updateBlog(..))  and !bean('tianmayingBlog')

其它切点描述符

其它可用的描述符包括:

  • args()

  • @args()

  • execution()

  • this()

  • target()

  • @target()

  • within()

  • @within()

  • @annotation

当你有更加复杂的切点需要描述时,你可能可以用上这些描述符,通过这些你可以设置目标类实现的接口、方法和类拥有的标注等信息。具体可以参考Spring的官方文档。

这里一共有9个描述符,execution()前面已经详细讨论过,其它几个可以做一个简单的分类:

  • this()是用来限定方法所属的类,比如this(com.tianmaying.service.BlogServiceInterface)表示实现了com.tianmaying.service.BlogServiceInterface的所有类。如果this括号内是具体类而不是接口的话,则表示单个类。

  • @annotation表示具有某个标注的方法,比如@annotation(org.springframework.transaction.annotation.Transactional)表示被Transactional标注的方法

  • args 表示方法的参数属于一个特定的类

  • within 表示方法属于一个特定的类

  • target 表示方法所属的类

  • 它们对应的加了@的版本则表示对应的类具有某个标注。

单独定义切点

详细了解了定义切点之后,在回顾上一节中的代码:

package com.tianmaying.aopdemo.aspect;import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect //1
@Component
public class LogAspect {@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))") //2private void logPointCut() {}@AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3public void logBookingStatus(boolean retVal) {  //4if (retVal) {System.out.println("booking flight succeeded!");} else {System.out.println("booking flight failed!");}}
}

可以看到通过标注方式定义切点只需要两个步骤:

  1. 定义一个空方法
  2. 使用@Piontcut标注,填入切点表达式

@AfterReturning(pointcut = "execution(* com.tianmaying.aopdemo..*.bookFlight(..))", returning = "retVal")中通过pointcout = "logPointCut"引用了这个切点。当然也可以在@AfterReturning()直接定义切点表达式,如:

@AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3

推荐使用前一种方法,因为这样可以在多个通知中复用切点的定义。

切点定义实例

这里我们给出一些切点的定义实例。

@Pointcut("execution(public * *(..))") // 1
private void anyPublicOperation() {}@Pointcut("within(com.xyz.someapp.web..*))") // 2
private void inTrading() {}@Pointcut("anyPublicOperation() && inTrading()") // 3
private void tradingOperation() {}@within(org.springframework.transaction.annotation.Transactional) // 4
private void transactionalClass() {}@annotation(org.springframework.transaction.annotation.Transactional) //5
private void transactionalMethod() {}

上面的代码定义了三个切点:

  1. 任意公共方法(实际应用中一般不会定义这样的切点)
  2. within(com.xyz.someapp.web包或者其子包下任意类的方法
  3. 同时满足切点1和切点2条件的切点,这里使用了&&符号
  4. 标注了Transactional的类的方法
  5. 标注了Transactional的方法

定义通知

依然回到TimeRecordingAspect的代码:

@Aspect
@Component
public class TimeRecordingAspect {@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))")private void timeRecordingPointCut() {}@Around("timeRecordingPointCut()") //1public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {  //2long start = System.currentTimeMillis();Object retVal = pjp.proceed(); // 3long duration = System.currentTimeMillis() - start;System.out.println(String.format("time for booking flight is %d seconds", duration));return retVal;}
}

定义了切点之后,我们需要定义何时调用recordTime方法记录时间,即需要定义通知。

AspectJ提供了五种定义通知的标注:

  • @Before:前置通知,在调用目标方法之前执行通知定义的任务
  • @After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务
  • @After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
  • @After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
  • @Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务

通过标注定义通知只需要两个步骤:

  1. 将以上五种标注之一添加到切面的方法中
  2. 在标注中设置切点的定义

创建环绕通知

环绕通知相比其它四种通知有其特殊之处。环绕通知本质上是将前置通知、后置通知和异常通知整合成一个单独的通知。

@Around标注的方法,该方法必须有一个ProceedingJoinPoint类型的参数,比如上面代码中的recordTime的签名:

public Object recordTime(ProceedingJoinPoint pjp) throws Throwable

在方法体中,需要通过这个参数,以joinPoint.proceed();的形式调用目标方法。注意在环绕通知中必须进行该调用,否则目标方法本身的执行就会被跳过。

比如在recoredTime的实现中:

long start = System.currentTimeMillis();
Object retVal = pjp.proceed();
long duration = System.currentTimeMillis() - start;
System.out.println(String.format("time for booking flight is %d seconds", duration));

在目标方法调用前首先记录系统时间,然后通过pjp.proceed()调用目标方法,调用完之后再次记录系统时间,即可计算出目标方法的耗时。

处理通知中参数

有时我们需要给通知中的方法传递目标对象的一些信息,比如传入目标业务方法的参数。

在前面的代码中我们曾经通过@AfterReturning(pointcut = "logPointCut()", returning = "retVal")在通知中获取目标业务方法的返回值。获取参数的方式则需要使用关键词是args

假设需要对系统中的accountOperator方法,做Account的验证,验证逻辑以切面的方式显示,示例如下:

@Before("com.tianmaying.UserService.accountOperator() && args(account,..)")
public void validateAccount(Account account) {// ...// 这可以获取传入accountOperator中的Account信息
}

args()中参数的名称必须跟切点方法的签名中(public void validateAccount(Account account))的参数名称相同。如果使用切点函数定义,其中的参数名称也必须与通知方法签名中的参数完全相同,例如:

@Pointcut("com.tianmaying.UserService.accountOperator() && args(account,..)")
private void accountOperation(Account account) {}@Before("accountOperation(account)")
public void validateAccount(Account account) {// ...
}

小节

AOP的知识就介绍到这里,更复杂的场景还需要了解AOP更深入的一些知识,比如:

  • AOP的生成代理的方式
  • 多个切面的顺序
  • 更复杂的参数类型(如泛型)
  • 使用AspectJ的切面
  • ...

感兴趣的同学可以继续深入学习,最好的学习材料就是Spring的官方文档。

天码营外围的网站开发的也基本只使用了我们介绍的这些知识点,可见这些关键知识点足以解决大部分复杂场景,确实需要用到更高级的特性时,再去参考文档即可。

版权声明本文由 David创作,转载需署名作者且注明文章出处参考代码

要获取本文的参考代码,请访问: https://www.tianmaying.com/tutorial/spring-aop-point-advice/repo

from: https://www.tianmaying.com/tutorial/spring-aop-point-advice

Spring AOP中定义切点(PointCut)和通知(Advice)相关推荐

  1. Spring AOP中定义切点PointCut详解

    1.AOP是什么? 软件工程有一个基本原则叫做"关注点分离"(Concern Separation),通俗的理解就是不同的问题交给不同的部分去解决,每部分专注于解决自己的问题.这年 ...

  2. java方法设置切点_java相关:Spring AOP中定义切点的实现方法示例

    java相关:Spring AOP中定义切点的实现方法示例 发布于 2020-6-6| 复制链接 摘记: 本文实例讲述了Spring AOP中定义切点的实现方法.分享给大家供大家参考,具体如下:一 配 ...

  3. Spring AOP中定义切点的详细介绍

    AOP的切点定义大部分的人都知道excecution()其实在AOP中有很多关旭切点的参数,用好可这些参数可以让我们的工作事半功倍, 一.excecution() 关于excecution必须要介绍的 ...

  4. java切点配置_Spring AOP中定义切点的实现方法示例

    本文实例讲述了Spring AOP中定义切点的实现方法.分享给大家供大家参考,具体如下: 一 配置 xmlns:xsi="http://www.w3.org/2001/XMLSchema-i ...

  5. Spring AOP 中的切点是什么?如何定义切点?

    Spring AOP 中的切点是什么?如何定义切点? 什么是切点? 在 Spring AOP 中,切点(Pointcut)是指一组连接点(Join Point)的集合.连接点是程序执行过程中的某个特定 ...

  6. Spring AOP中是如何注册Advisor的?

    前置博文: Spring AOP中如何为Bean创建代理? Spring AOP中是如何注册Advisor的? Spring AOP如何为目标方法创建拦截器链? Spring AOP拦截器调用的实现 ...

  7. Spring AOP中pointcut 切点详解

    Spring AOP中pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的. Pointcut可以有下列方 ...

  8. AOP中的切点、切面、通知等

    在AOP中,切点.切面和通知是三个核心概念,下面分别进行介绍. 切点(Pointcut) 切点是一个表达式,用于描述哪些类的哪些方法会被拦截.通常情况下,切点会使用表达式语言(如AspectJ)来定义 ...

  9. SpringBoot AOP中JoinPoint的用法和通知切点表达式

    前言 上一篇文章讲解了springboot aop 初步完整的使用和整合 这一篇讲解他的接口方法和类 JoinPoint和ProceedingJoinPoint对象 JoinPoint对象封装了Spr ...

最新文章

  1. java怎么给类中的私有变量赋值_Java核心技术笔记分享------第二章 类与对象
  2. Segmentation fault (core dumped) -llinux系统内存错误报错信息
  3. 【离散数学】集合的划分与覆盖
  4. Google Go:初级读本
  5. 因“突发肾结石” 孙宇晨宣布取消与巴菲特的午餐会面
  6. Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview
  7. 北航操作系统课程-20200409课堂小测-进程同步
  8. 如何正确下载安全无毒的局域网、内网即时通讯软件
  9. bilibili弹幕爬虫, 2019-1-10
  10. 如何创建自己的 Google Chrome 扩展程序
  11. 图像修复(Image Restoration)
  12. [量子客] 12月全球量子资讯周报
  13. 华为笔面试经历-前端开发-2020年春招
  14. 基姆拉尔森时间计算公式
  15. 微软云的一些说明(整理)
  16. 大学计算机作业互评评语简短,【同学互评评语100字】同学作业互评评语(2).doc...
  17. Android播放视频快进帧预览图完美解決方案
  18. 昨晚开始了为期3个月的初级德语课,课上大家跟老师咿咿呀呀,仿佛回到了蒙学时代,感觉还是不错的!在blog里增加一个GERMAN随笔分类主要是方便自己随时学习,勿怪!Vielen Dank!...
  19. 139邮箱smtp地址和端口_常用的邮箱服务器(SMTP、POP3)地址、端口
  20. linux docker查找镜像文件,搜索/下载/构建自定义/删除Docker镜像,运行和删除Docker容器的方法...

热门文章

  1. 【大数据-Hadoop】dbeaver
  2. 关于Linux Kernel中的宏定义likely和unlikely
  3. Arcface v1 论文翻译与解读
  4. 一场低调的逆袭:清华文化如何改变了王兴和美团?
  5. Web3.0来了!玩法变了
  6. 专访格灵深瞳CTO赵勇:为 计算机视觉 赋予智慧的光芒
  7. linux关闭涉及安全的服务,Linux中关闭不必要服务减少漏洞
  8. 白话Elasticsearch27-深度探秘搜索技术之误拼写时的fuzzy模糊搜索技术
  9. 并发编程-13线程安全策略之两种类型的同步容器
  10. json html显示中文乱码,后台请求json文件,中文出现乱码