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

文章目录

  • 前言
    • 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 {@MethodLogpublic 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 {@Autowiredprivate 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

源码0积分下载地址:https://download.csdn.net/download/scm_2008/87375584

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue和FastAPI实现前后端分离

前言 近期接触了一些开源大模型应用服务&#xff0c;发现很多用的都是FastAPI web框架&#xff0c;于是乎研究了一下它的优势&#xff0c;印象最深有两个&#xff1a;一个是它的异步处理性能比较好&#xff0c;二是它可以类似java swagger的API交互文档&#xff0c;这个对应前…

服务器远程桌面连接不上怎么办?

随着互联网的发展和远程办公的兴起&#xff0c;服务器远程桌面连接成为了许多企业和个人不可或缺的工具。偶尔我们可能会碰到服务器远程桌面连接不上的情况&#xff0c;这时候我们需要找到解决办法&#xff0c;确保高效地远程访问服务器。 天联组网——突破远程连接障碍 在我们…

性能优化 - 你知道dns-prefetch有什么用吗

难度级别:中级及以上 提问概率:50% 我们在HTML文档里写一个script标签,为src属性指定Javascript文件网络地址,这是一件再平凡不过的事情。当浏览器加载HTML文档,加载到这个script标签的时候,就会去下载Javascript文件。而在下载之前,就…

c# wpf LiveCharts 饼图 简单试验

1.概要 c# wpf LiveCharts 饼图 简单试验 2.代码 <Window x:Class"WpfApp3.Window5"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…

element-ui的年份范围选择器,选择的年份需等于或小于当前年份,选择的年份范围必须在三年之内

写在前面 日期限制处理&#xff08;禁用&#xff09;&#xff0c;下面我以我这边的需求为例&#xff0c; 选择的年份需等于或小于当前年份 选择的年份范围必须在三年之内 1.限制起始日期小于截止日期 1&#xff09;根据用户选中的开始日期&#xff0c;置灰不可选的日期范围&…

12+炫酷地图可视化效果,这次还真的有源码。

2023-09-17 22:35贝格前端工场 Hi&#xff0c;大家好&#xff0c;我是贝格前端工场&#xff0c;之前分享过各类UI图、动图、3D图、流程图&#xff0c;好多粉丝朋友给我要源文件&#xff0c;因为种种原因&#xff0c;无法提供。 本次分享12个炫酷的地图可视化效果&#xff0c;…

P8707 [蓝桥杯 2020 省 AB1] 走方格

原题链接&#xff1a;[蓝桥杯 2020 省 AB1] 走方格 - 洛谷 目录 1.题目描述 2.思路分析 3.代码实现 1.题目描述 2.思路分析 题目大意&#xff1a;现在有个人站在第 1 行第 1 列&#xff0c;要走到第 i 行第 j 列&#xff08;每次只能向右或者向下走&#xff09;&#xff0…

wireshark抓包新手使用教程

Wireshark是非常流行的网络封包分析软件&#xff0c;可以截取各种网络数据包&#xff0c;并显示数据包详细信息。常用于开发测试过程各种问题定位。本文主要内容包括&#xff1a; 1、Wireshark软件下载和安装以及Wireshark主界面介绍。 2、WireShark简单抓包示例。通过该例子学…

【C++航海王:追寻罗杰的编程之路】探寻实用的调试技巧

目录 1 -> 什么是bug&#xff1f; 2 -> 调试是什么&#xff1f;有多重要&#xff1f; 2.1 -> 调试是什么&#xff1f; 2.2 -> 调试的基本步骤 2.3 -> Debug和Release的介绍 3 -> Windows环境调试介绍 3.1 -> 调试环境的准备 3.2 -> 学会快捷键…

博客部署004-centos安装mysql及redis

1、如何查看当前centos版本&#xff1f; cat /etc/os-release 2、安装mysql 我的是centos8版本&#xff0c;使用dnf命令 2.1 CentOS 7/8: sudo yum install -y mysql-community-server 或者在CentOS 8上&#xff0c;使用DNF:&#x1f31f; sudo dnf install -y mysql-ser…

python 中如何遍历时间日期?

需要使用到 datetime 模块来遍历两个日期之间的所有日期。下面是一个遍历2024年1月1日到2024年1月31日之间所有日期的例子&#xff1a; from datetime import datetime,timedelta# 设置起始时间 start_time datetime(2024,1,1) end_time datetime(2024,1,31)#当当前日期小…

【工具-工具指南】

项目-开发工具 ■ 编辑器■ Xmind ■ UI交互设计■ AxureRP9 ■ 项目管理■ boardmix■ excalidraw ■ Markdown■ MarkText■ Typora■ Ulysses■ Notable■ VNote■ Mou■ Bears■ Notion■ 有道云■ 印象笔记 ■ 硬件画图■ AD■ Allegro■ PADS■ Eagle■ Altium■ Fritzin…

计算机硬件组成

计算机硬件组成 基本组成核心组件连接方式与总线架构与技术特殊组件总结脑图 基本组成 CPU: 执行指令和进行数据处理内存: 存储程序和数据&#xff0c;分为RAM和ROM主板: 连接所有硬件的平台&#xff0c;传输电子信号输入设备: 如键盘、鼠标等输出设备: 如显示器、打印机等 核…

javaWeb城市公交查询系统的设计与实现

一、选题背景 随着低碳生活的普及&#xff0c;人们更倾向于低碳环保的出行方式&#xff0c;完善公交系统无疑具有重要意义。公交是居民日常生活中最常使用的交通工具之一&#xff0c;伴随着我国经济繁荣和城市人口增长&#xff0c;出行工具的选择也变得越来越重要。政府在公共…

C++——优先级队列

前言&#xff1a;这篇文章我们继续来分享一个c的容器——优先级队列。 一.理解优先级 何为优先级一说&#xff1f;实际上就是有顺序的意思。 优先级队列&#xff0c;即有顺序的队列&#xff0c;是一个无需我们自己进行排序操作&#xff0c;在数据传入时就会由容器自己排好序的…

紫光展锐T610平台_4G安卓核心板方案定制开发

紫光展锐T610核心板配备Android 11操作系统&#xff0c;采用12nm制程工艺。该处理器CPU由2颗基于Cortex-A75架构的大核心和6颗基于Cortex-A55架构的小核心组成&#xff0c;最高主频为1.8GHz。GPU采用的是614.4MHz的Mali G52&#xff0c;可以流畅播放2400*1080分辨率视频&#x…

浅述安防视频监控平台EasyCVR视频汇聚管理系统运维管理能力

智慧安防监控EasyCVR视频管理平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。国标GB28181协议视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、…

【Qt 学习笔记】Qt控件概述

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt控件概述 文章编号&#xff1a;Qt 学习笔记 / 14 文章目录 Qt控件概…

第6章 6.1.1 文本格式化 sprintf函数(MATLAB入门课程)

sprintf函数源自 C 语言标准库中的同名函数&#xff0c;这个函数在 C 语言中用于创建格式化的字符串&#xff0c;且使用频率非常高。作为一门高级编程语言&#xff0c;MATLAB借鉴了 C 语言和其他编程语言中的许多特性和命名惯例。在MATLAB中&#xff0c;sprintf函数主要有两种用…

企业如何设计和实施有效的网络安全演练?

现实世界中&#xff0c;武装部队一直利用兵棋推演进行实战化训练&#xff0c;为潜在的军事冲突做准备。随着当今的数字化转型&#xff0c;同样的概念正在以网络安全演习的形式在组织中得到应用&#xff0c;很多企业每年都会基于合理的网络攻击场景和事件响应做一些测试和模拟。…