【Spring AOP】@Aspect结合案例详解(一): @Pointcut使用@annotation + 五种通知Advice注解

news2024/11/20 18:41:12

文章目录

  • 前言
    • AOP与Spring AOP
    • @Aspect简单案例快速入门
  • 一、@Pointcut
    • @annotation
  • 二、五种通知Advice
    • 1. @Before前置通知
    • 2. @After后置通知
    • 3. @AfterRunning返回通知
    • 4. @AfterThrowing异常通知
    • 5. @Around环绕通知
  • 总结


前言

在微服务流行的当下,在使用SpringCloud/Springboot框架开发中,AOP使用的非常广泛,尤其是@Aspect注解方式当属最流行的,不止功能强大,性能也很优秀,还很舒心!所以本系列就结合案例详细介绍@Aspect方式的切面的各种用法,力求覆盖日常开发中的各种场景。本文带来的案例是:打印Log,主要介绍@Pointcut切点表达式的@annotation方式,以及 五种通知Advice注解@Before、@After、@AfterRunning、@AfterThrowing、@Around

AOP与Spring AOP

在正式开始之前,我们还是先了解一下AOP与Spring AOP~
在软件开发过程中,有一些逻辑横向遍布在各个业务模块中,像权限、监控、日志、事务、异常重试等等,所以造成代码分散且冗余度高,和业务代码混夹在一起, 写起来不够优雅,改起来更是一种折磨!为了解决这些问题,AOP(Aspect Oriented Programming:面向切面编程)也就应运而生了,它是一种编程思想,就像OOP(面向对象编程)也是一种编程思想,所以AOP不是某种语言或某个框架特有的,它实现的是将横向逻辑与业务逻辑解耦,实现对业务代码无侵入,从而让我们更专注于业务逻辑本身,本质是在不改变原有业务逻辑的情况下增强横切逻辑
在这里插入图片描述

在Spring中,AOP共有3种实现方式

  • Spring1.2 基于接口的配置:Spring最早的AOP实现是完全基于接口,虽然兼容,但已经不推荐了.
  • Spring2.0+ schema-based 配置 :Spring2.0之后,提供了 schema-based 配置,也就是xml的方式配置.
  • Spring2.0+ @Aspect配置:Spring2.0之后,也提供了 @Aspect 基于注解的实现方式,也就是本文的主角,也是目前最方便、最广泛使用的方式!(推荐)

@Aspect简单案例快速入门

@Aspect注解方式,它的概念像@Aspect、@Pointcut、@Before、@After、@Around等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的,主要有两大核心

  • 定义[切入点]:使用 @Pointcut 切点表达式,你可以理解成类似于正则表达式的强大东东。(本文先只介绍@annotation方式)
  • 定义[切入时机] 和 [增强处理逻辑]五种通知Advice注解 对[切入点]执行增强处理, 包括:@Before、@After、@AfterRunning、@AfterThrowing、@Around

如果没有AOP基础,对于概念可能会比较懵,所以先上一个最简单案例,基于@Aspect注解方式如何实现切面:

// @Aspect和@Component定义一个切面类
@Aspect
@Component
public class MethodLogAspect {
    // 核心一:定义切点(使用@annotation方式)
    @Pointcut(value = "@annotation(com.tiangang.aop.MethodLog)")
    public void pointCut() {

    }
    // 核心二:对切点增强处理(这是5种通知中的前置通知)
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知:" + joinPoint);
    }
}

一共没有几行代码,就非常简单实现在方法执行前打印日志的功能注解类如下(对于打上这个注解的方法 都会被切面类增强处理):

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLog {

}

pom.xml依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

ok,接下来我们分别具体来看这两大核心**@PointcutAdvice**.


一、@Pointcut

@Pointcut切点表达式非常丰富,可以将 方法(method)、类(class)、接口(interface)、包(package) 等作为切入点,非常灵活,常用的有@annotation、@within、execution等方式,由于篇幅原因,本文先只介绍@annotation方式。

@annotation

@annotation方式是指:切入点 是指定作用于方法上的注解,即被Spring扫描到方法上带有该注解 就会执行切面通知。

@Pointcut(value = "@annotation(com.tiangang.aop.MethodLog)")
public void pointCut() {

}

案例给出的@Pointcut说明:
语法:@Pointcut(value = "@annotation(注解类名)")

注:只有注解类名是动态的,其它是固定写法.


二、五种通知Advice

通过@Pointcut定义的切点,共有五种通知Advice方式:

注解说明
@Before前置通知,在被切的方法执行前执行
@After后置通知,在被切的方法执行后执行,比return更后
@AfterRunning返回通知,在被切的方法return后执行
@AfterThrowing异常通知,在被切的方法抛异常时执行
@Around环绕通知,这是功能最强大的Advice,可以自定义执行顺序

执行顺序如下:

在这里插入图片描述

我这里在Service里定义了一个除法方法divide(),在这个方法也打上@MethodLog注解,让它可以被切面横切。

@Service
public class DemoService {
    @MethodLog
    public Integer divide(Integer a, Integer b) {
        System.out.printf("方法内打印: a=%d  b=%d %n", a, b);
        return a / b;
    }
}

用于测试的controller代码,都很简单:

@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private DemoService demoService;

    @GetMapping("/divide")
    public Integer divide(Integer a, Integer b) {
        return demoService.divide(a, b);
    }
}

1. @Before前置通知

前置通知在被切的方法执行之前执行!

@Before("pointCut()")
public void before(JoinPoint joinPoint) throws NoSuchMethodException {
    printMethod(joinPoint, "[前置通知before]");
}

注解语法@Before("切点方法名()")

注:只有《切点方法名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint)

这里有个非常重要参数JoinPoint:连接点 。因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法. 里面有三个常用的方法:

  • getSignature()获取签名:

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();

    通过signature可以获取名称 getName() 和 参数类型 getParameterTypes()

  • getTarget()获取目标类:
    Class<?> clazz = joinPoint.getTarget().getClass();

    如果被切的类 是 被别的切面切过的类,可以使用AopUtils.getTargetClass获取一个数组,再从数组中找你期望的类。

    import org.springframework.aop.support.AopUtils;
    Class<?>[] targets = AopUtils.getTargetClass(joinPoint.getTarget()).getInterfaces();
    
  • getArgs()获取入参值

    Object[] args = joinPoint.getArgs()

基于这3个方法,可以轻松打印:被切的类名、方法名、方法参数值、方法参数类型等,printMethod方法如下:

private void printMethod(JoinPoint joinPoint, String name) throws NoSuchMethodException {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Class<?> clazz = joinPoint.getTarget().getClass();
    Method method = clazz.getMethod(signature.getName(), signature.getParameterTypes());
    System.out.printf("[MethodLogAspect]切面 %s 打印 -> [className]:%s  ->  [methodName]:%s  ->  [methodArgs]:%s%n", name, clazz.getName(), method.getName(), Arrays.toString(joinPoint.getArgs()));
}

调用测试类,输出结果如下:

[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 

2. @After后置通知

后置通知在被切的方法执行之后执行,无论被切方法是否异常都会执行!

@After("pointCut()")
public void after(JoinPoint joinPoint) throws NoSuchMethodException {
    printMethod(joinPoint, "[后置通知after]");
}

注解语法@After("切点方法名()")

注:只有《切点方法名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint)

调用测试类,输出结果如下:

[MethodLogAspect]切面 [前置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]

3. @AfterRunning返回通知

返回通知在被切的方法return后执行,带有返回值,如果被切方法异常则不会执行!

这里多了一个参数Object result,注解上也多了一个参数:returning

@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) throws NoSuchMethodException {
    printMethod(joinPoint, "[返回通知afterReturning]");
    System.out.printf("[MethodLogAspect]切面 [返回通知afterReturning] 打印结果 -> result:%s%n", result);
}

注解语法@AfterReturning(value = "切点方法名(), returning = "返回值参数名")

注:只有《切点方法名》和 《返回值参数名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint, Object result)

调用测试类,输出结果如下:

[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 
[MethodLogAspect]切面 [返回通知afterReturning] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [返回通知afterReturning] 打印结果 -> result:5
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]

4. @AfterThrowing异常通知

异常通知只在被切方法异常时执行,否则不执行。

这里多了一个参数Exception e,表示捕获所有异常,也可以设置为具体某一个异常,例如NullPointerException、RpcException等等。注解上也多了一个参数:throwing

@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) throws NoSuchMethodException {
    printMethod(joinPoint, "[异常通知afterThrowing]");
    System.out.printf("[MethodLogAspect]切面 [异常通知afterThrowing] 打印异常 -> Exception:%s%n", e);
}

注解语法@AfterThrowing(value = "切点方法名(), throwing = "异常参数名")

注:只有《切点方法名》和 《异常参数名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint, Exception e)

调用测试类,输出结果如下:

[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 0]
方法内打印: a=10  b=0 
[MethodLogAspect]切面 [异常通知afterThrowing] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 0]
[MethodLogAspect]切面 [异常通知afterThrowing] 打印异常 -> Exception:java.lang.ArithmeticException: / by zero
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 0]
2023-01-06 21:05:06.536 ERROR 15436 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

5. @Around环绕通知

环绕通知方法可以包含上面四种通知方法,是最全面最灵活的通知方法。

这里的参数类型和其它通知方法不同,从JoinPoint 变为ProceedingJoinPoint

@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    printMethod(joinPoint, "[环绕通知around][proceed之前]");
    // 执行方法, 可以对joinPoint.proceed()加try catch处理异常
    Object result = joinPoint.proceed();
    System.out.printf("[MethodLogAspect]切面 [环绕通知around][proceed之后]打印 -> [result]:%s%n", result);
    return result;
}

注解语法@Around("切点方法名()")

注:只有《切点方法名》是动态的,其它是固定写法.

方法语法:public Object 方法名(ProceedingJoinPoint joinPoint) throws Throwable

调用测试类,输出结果如下:

[MethodLogAspect]切面 [环绕通知around][proceed之前] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 
[MethodLogAspect]切面 [返回通知afterReturning] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [返回通知afterReturning] 打印结果 -> result:5
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [环绕通知around][proceed之后]打印 -> [result]:5

总结

本文主要说明了,如何通过@Aspect定义一个切面类,并结合打印Log案例主要介绍了两大核心的用法:

  • @Pointcut使用 @annotation 方式定义切入点
  • 五种通知(Advice)注解用法:@Before、@After、@AfterRunning、@AfterThrowing、@Around

如果感觉不错,欢迎关注我 天罡gg 分享更多干货: https://blog.csdn.net/scm_2008
大家的「关注 + 点赞 + 收藏」就是我创作的最大动力!谢谢大家的支持,我们下文见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式基础篇3——前端开发基础知识

前端技术对比一、ES61、简介2、什么是 JavaScript3、ES6新特性3.1 let3.2 const3.3 解构表达式3.4 字符串扩展3.5 函数优化3.6 对象优化3.7 map 和 reduce3.8 Promise3.9 模块化二、Vue1、MVVM 思想2、Vue 简介3、Vue 入门案例4、Vue 指令插值表达式v-text、v-htmlv-bindv-mode…

景区地图最短路径快速实现

1 前言以前粗略学习了一下在地图中实现最短路径&#xff0c;并在切图工具中实现了自动处理生成导航相关数据。https://blog.csdn.net/bq_cui/article/details/86795213最近发现工具实现的结果&#xff0c;错误一大堆。这次再详细捋一捋整个步骤&#xff0c;感兴趣的同学可以试一…

ctemplate 的安装和使用

ctemplate 用于linux下的web开发&#xff0c;可以动态生成一个html网页&#xff0c;这里的 “ 动态 ” 指的是网页的数据不是固定的&#xff0c;可以使用变量来填充网页内容。 目录 1、下载ctemplate 2、安装 ctemplate 3、使用ctemplate库 1、下载ctemplate 可以在gite…

前端对接微信公众号网页开发流程,授权对接

前面讲到 前端对接微信公众号网页开发流程&#xff0c;前期配置&#xff0c;本篇文章主要详细介绍关于公众号的授权对接。 一、引入微信js-sdk 在需要调用 JS 接口的页面引入如下 JS 文件 http://res.wx.qq.com/open/js/jweixin-1.6.0.js如需进一步提升服务稳定性&#xff0…

如何在windows上使用VMware安装macOS虚拟机

如何在windows上使用VMware安装macOS虚拟机一、准备工作1.1 安装 VMware1.2 下载macOS的安装包1.3 下载VMware虚拟机解锁安装苹果系统工具二、解锁VMware支持macOS安装2.1 关闭已经打开的VMware软件2.2 安装VMware Workstation Unlocker三、VMware创建虚拟机3.1 解压缩macOS的安…

【蓝桥杯】X 进制减法

难点一&#xff1a;base base * s[i] % mod;//***当前位乘以x&#xff0c;x要*s[i],一直要乘到个位的进制难点二&#xff1a;当当前该位的的进制位&#xff0c;为max(max(a[i]1, b[i]1), 2)的时候&#xff0c;结果最小ACcode:#include<iostream>using namespace std;con…

性能优化系列之『混合式开发: React Native内核及优势介绍』

文章の目录一、React Native愿景二、技术优势1、技术2、效率3、发版三、底层内核1、RN&#xff1a;helloWorld.jsx2、iOS&#xff1a;helloWorld.m3、Android&#xff1a;helloWorld.xml4、Web&#xff1a;helloWorld.html四、行业背景五、选型建议写在最后一、React Native愿景…

【计算机体系结构基础】指令流水线

单周期处理器 简要描述单周期处理器的执行过程&#xff1a; PC从指令存储器中读取指令 取指后译码得出相关的控制信号读取regfile&#xff08;寄存器堆&#xff09; 运算器对regfile中取出的操作数进行计算&#xff0c;将计算的结果写回通用寄存器堆或者得到访存指令的地址或…

边缘数据采集网关如何实现PLC远程上下载

边缘数据采集网关&#xff0c;又称边缘计算网关、工业物联网网关&#xff0c;是连接工业设备与通信网络的桥梁&#xff0c;可以实现不同协议之间的解析转换&#xff0c;打造高效实时的数据采集系统&#xff0c;并借助边缘计算规则对数据进行清洗过滤&#xff0c;适配云平台实现…

QT 学习笔记(十五)

文章目录一、UDP 通信过程1. Linux 下的 UDP 通信过程2. QT 下的 UDP 通信过程3. 在 QT 中实现 UDP 通信的流程4. TCP/IP 和 UDP的区别二、UDP 文本发送1. UDP 文本发送实例演示2. UDP 广播3. UDP 组播三、UDP 文本发送实现代码1. 主窗口头文件 widget.h2. 主窗口源文件 widget…

论文创新及观点

FEW-SHOT TEXT CLASSIFICATION WITH DISTRIBUTIONAL SIGNATURES 任务 Given an N-way K-shot classification task 论文设计图像 数据集 20 Newsgroups is comprised of informal discourse from news discussion forums (Lang, 1995).Documents are organized under 20 to…

数据分析-深度学习 Day2

目录&#xff1a;第一节 机器学习&深度学习介绍第二节 机器学习攻略一、机器学习的框架二、模型训练攻略三、针对Optimization Issue的优化&#xff0c;类神经网络训练不起来怎么办(一) 局部最优点和鞍点(二) 批处理和momentum(三) 自动调节学习率Learning rate(四) 损失函…

vue实现微信端和企业微信端扫码

前要&#xff1a;微信端调用微信的扫一扫和企业微信端调用企业微信的扫一扫获取订单码查询&#xff01;&#xff01; 一、微信端扫一扫 这里使用的是uniapp框架调用微信的内置sdk扫码防伪溯源&#xff01;http引入或者npm安装模块&#xff1a; //public/index.html <!DOCT…

《设计模式》命令模式

《设计模式》设计模式的基本原则 《设计模式》单例模式 《设计模式》工厂模式 《设计模式》原型模式 《设计模式》建造者模式 《设计模式》适配器模式 《设计模式》桥接模式 《设计模式》装饰者模式 《设计模式》组合模式 《设计模式》外观模式 《设计模式》享元模式 《设计模式…

【Java开发】Spring Cloud 03 :Spring Boot 项目搭建

为了体验从 0 到 1 的微服务改造过程&#xff0c;我们先使用 Spring Boot 搭建一个基础版的优惠券平台项目&#xff0c;等学习到 Spring Cloud 的时候&#xff0c;我们就在这个项目之上做微服务化改造&#xff0c;将 Spring Cloud 的各个组件像添砖加瓦一样集成到项目里。上一章…

jacoco:java代码覆盖率实践

文章目录一、jacoco基本了解二、实践准备三、jacoco使用3.1 插桩3.2 dump:覆盖率文件导出3.3 report:可视化报告3.4 merge:合并覆盖率文件四、相关命令扩展4.1 javaagent4.2 dump4.3 merge4.4 report五、资源链接一、jacoco基本了解 jacoco是一款面向java的代码覆盖率工具&…

linux系统中C++中构造与析构函数以及this的使用方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;C里面的基本语法结构以及对应的操作方法。 目录 第一&#xff1a;构造函数与析构函数 第二&#xff1a;this指针 第一&#xff1a;构造函数与析构函数 什么是构造函数&#xff1f;构造函数在对象实例化时被系统自动调用&a…

xshell连接Linux一直失败解决方法

文章目录解决对象方法配置防火墙关闭Linux防火墙关闭Windows防火墙xshell连接Linux一直失败解决方法 解决对象 可能出现以下两个问题&#xff1a; Linux防火墙已关闭和Windows防火墙已经关闭配置好 vim /etc/sysconfig/network-scripts/ifcfg-ens33 方法 配置 这个是最容易…

linux系统中实现C++中继承和重载的方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何实现C中继承和重载的功能。 第一&#xff1a;C中的继承功能 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类&#xff0c;这使得创建和维护一个应用程序变得更容易。这样做&#xff0…

CLRNet: Cross Layer Refinement Network for Lane Detection

Paper name CLRNet: Cross Layer Refinement Network for Lane Detection Paper Reading Note URL: https://arxiv.org/pdf/2203.10350.pdf TL;DR CVPR 2022 文章&#xff0c;自动驾驶公司飞步科技与浙大联合出品。lane anchor-based 方案&#xff0c;在多个数据集上取得 …