14. Spring AOP 的组成和实现

目录

1. Spring AOP 简介

2. AOP 的组成

2.1 切面(Aspect)

2.2 连接点(Join Point)

2.3 切点(Pointcut)

2.4 通知(Advice)

3. Spring AOP的实现

3.1 新建项目

3.2 添加 AOP 框架支持 

3.3 定义切面、切点和通知

4. 切点表达式说明

5. 练习:使用 AOP 统计 UserController 每个方法的执行时间


1. Spring AOP 简介

AOP 是一种思想,Spring AOP 是这种思想的具体实现。

OOP:面向对象编程

AOP:面向切面编程

AOP 面向切面编程,就是对某一类事情的集中处理。

比如,我们需要在 CSDN 上进行编辑博客、发布博客、删除博客等操作,这些功能都是需要进行权限校验的,判断是否登录。

开发三阶段

对于公共方法的处理:

  1. (初级阶段)每个方法都去实现
  2. (中级阶段)把同一类功能抽取成公共方法
  3. (高级阶段)采用 AOP 的方式,对代码无侵入实现

除了统⼀的用户登录判断之外,AOP 还可以实现:

  • 统⼀日志记录
  • 统一方法执行时间统计
  • 统一的返回格式设置
  • 统一的异常处理
  • 事务的开启和提交等
统一方法执行时间统计项目监控:监控项目请求流量、监控接口的响应时间甚至每个方法的响应时间
统一的返回格式设置

httpstatus: HTTP状态码

code: 业务状态码(后端响应成功不代表业务办理成功) 

msg: 业务处理失败返回的信息

data: 

也就是说使用 AOP 可以扩充多个对象的某个能力,所以 AOP 可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善

2. AOP 的组成

2.1 切面(Aspect)

切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。

切面是包含了:通知、切点和切面的类,相当于 AOP 实现的某个功能的集合。

2.2 连接点(Join Point)

应⽤执行过程中能够插入切面的⼀个点,这个点可以是方法调用时、抛出异常时,甚至修改字段 时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

连接点相当于需要被增强的某个 AOP 功能的所有方法。

2.3 切点(Pointcut)

Pointcut 是匹配 Join Point 的谓词。 Pointcut 的作用就是提供⼀组规则(使用 AspectJ pointcut expression language 来描述)来匹配 Join Point,给满足规则的 Join Point 添加 Advice。

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中⼀条⼀条 的数据)。

2.4 通知(Advice)

切面也是有目标的 ——它必须完成的工作。在 AOP 术语中,切面的工作被称之为通知

Spring 切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本 方法进行调用:

  • 前置通知使用 @Before:通知方法会在目标方法调用之前执行。
  • 后置通知使用 @After:通知方法会在目标方法返回或者抛出异常后调用。
  • 返回之后通知使用 @AfterReturning:通知方法会在目标方法返回后调用。
  • 抛异常后通知使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用。
  • 环绕通知使用户 @Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为。

切点相当于要增强的方法。 

AOP 整个组成部分的概念如下图所示,以多个页面都要访问用户登录权限为例:

既然说 AOP 是对一类事情的集中处理,那么我们就需要明确两点:

  1.  一类事情:处理对象的一个范围
  2.  集中处理:处理的内容是什么

我们通过生活中的一个例子来看一下:

比如,我们乘坐高铁需要安检

那么,我们需要处理的内容就是安检;处理的范围就是需要乘坐高铁的人。

此处乘坐高铁需要安检这件事情就是切面,处理的内容安检就是通知,处理的范围乘坐高铁的人就是切点,具体有哪些人就是连接点

切点是一个规则,事情的处理,最终作用在方法上。 

3. Spring AOP的实现

3.1 新建项目

3.2 添加 AOP 框架支持 

在 pom.xml 中添加如下配置:

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

3.3 定义切面、切点和通知

 我们先定义 UserController 类:

@RequestMapping("/user")
@RestController
public class UserController {// 获取用户信息@RequestMapping("/getInfo")public String getInfo(){return "get info...";}// 注册@RequestMapping("/reg")public String reg(){return "reg...";}// Login@RequestMapping("/login")public String login(){return "login...";}
}

运行后,成功访问: 

接下来,我们在 UserController 类中定义切面和切点: 

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {// 获取用户信息@RequestMapping("/getInfo")public String getInfo(){log.info("get info...");return "get info...";}// 注册@RequestMapping("/reg")public String reg(){log.info("reg...");return "reg...";}// Login@RequestMapping("/login")public String login(){log.info("login...");return "login...";}
}

在 LoginAspect 类中使用 @Before 注解(通知方法会在目标方法调用之前执行): 

@Slf4j
@Component
@Aspect
public class LoginAspect {@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut(){}@Before("pointcut()")public void doBefore(){log.info("do berore...");}
}

我们接着新建一个 TestController 类:

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("/hi")public String hi(){log.info("hi~");return "hi~";}
}

可以看到运行的结果中,并没有在控制台打印 @Before 中的内容: 

那么为什么没有执行呢?

我们再来看一下其他注解,@After(通知方法会在目标方法返回或者抛出异常后调用

@Slf4j
@Component
@Aspect
public class LoginAspect {@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut(){}@Before("pointcut()")public void doBefore(){log.info("do berore...");}@After("pointcut()")public void doAfter(){log.info("do after...");}
}

运行结果如下:

@AfterReturning(通知方法会在目标方法返回后调用)

@Slf4j
@Component
@Aspect
public class LoginAspect {@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut(){}@Before("pointcut()")public void doBefore(){log.info("do berore...");}@After("pointcut()")public void doAfter(){log.info("do after...");}@AfterReturning("pointcut()")public void doAfterReturning(){log.info("do after returning...");}
}

运行以上代码后: 

可以看到 :@AfterReturning 在 @After 之前被调用。

@AfterThrowing(通知方法会在目标方法抛出异常后调用)

我们首先在 UserController 类中加入异常:

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {// 获取用户信息@RequestMapping("/getInfo")public String getInfo(){log.info("get info...");return "get info...";}// 注册@RequestMapping("/reg")public String reg(){log.info("reg...");int a = 10/0;return "reg...";}// Login@RequestMapping("/login")public String login(){log.info("login...");return "login...";}
}

添加 @AfterThrowing 注解:

@Slf4j
@Component
@Aspect
public class LoginAspect {@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut(){}@Before("pointcut()")public void doBefore(){log.info("do berore...");}@After("pointcut()")public void doAfter(){log.info("do after...");}@AfterReturning("pointcut()")public void doAfterReturning(){log.info("do after returning...");}@AfterThrowing("pointcut()")public void doAfterThrowing(){log.info("do after throwing...");}
}

运行后可以看到: 

当正常返回时,执行 @AfterReturning 注解,当出现异常时,不会执行 @AfterReturning 注解;

当出现异常时,才会执行 @AfterThrowing 注解,当正常返回时,不会执行 @AfterThrowing 注解。

@Around(通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为):

添加  @Around 注解:

@Slf4j
@Component
@Aspect
public class LoginAspect {@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut(){}@Before("pointcut()")public void doBefore(){log.info("do berore...");}@After("pointcut()")public void doAfter(){log.info("do after...");}@AfterReturning("pointcut()")public void doAfterReturning(){log.info("do after returning...");}@AfterThrowing("pointcut()")public void doAfterThrowing(){log.info("do after throwing...");}@Around("pointcut()")public void doAround(ProceedingJoinPoint joinPoint){log.info("环绕通知执行之前...");try {joinPoint.proceed(); // 调用目标方法} catch (Throwable e) {throw new RuntimeException(e);}log.info("环绕通知执行之后...");}
}

运行后界面显示如下:

可以看到此时界面中不再有返回值,因此修改代码如下:

@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){Object oj = null;log.info("环绕通知执行之前...");try {oj = joinPoint.proceed(); // 调用目标方法} catch (Throwable e) {throw new RuntimeException(e);}log.info("环绕通知执行之后...");return oj;
}

此时可以看到成功返回并打印了值: 

我们再来看一下这段代码:

4. 切点表达式说明

AspectJ 支持三种通配符

  • * :匹配任意字符,只匹配一个元素(包,类,或方法,方法参数)
  • .. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
  • + :表示按照类型匹配指定类的所有类,必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的 所有子类包括本身

切点表达式由切点函数组成,其中 execution() 是最常用的切点函数,用来匹配方法,语法为: 

execution(<修饰符><返回类型><包.类.方法(参数)><异常>)

5. 练习:使用 AOP 统计 UserController 每个方法的执行时间

@Slf4j
@Component
@Aspect
public class LoginAspect {@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut(){}@Before("pointcut()")public void doBefore(){log.info("do berore...");}@After("pointcut()")public void doAfter(){log.info("do after...");}@AfterReturning("pointcut()")public void doAfterReturning(){log.info("do after returning...");}@AfterThrowing("pointcut()")public void doAfterThrowing(){log.info("do after throwing...");}@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint){Object oj = null;log.info("环绕通知执行之前...");try {oj = joinPoint.proceed(); // 调用目标方法} catch (Throwable e) {throw new RuntimeException(e);}log.info("环绕通知执行之后...");return oj;}/**** @param joinPoint 使用 AOP 统计 UserController 每个方法的执行时间* @return*/@Around("pointcut()")public Object doAroundCount(ProceedingJoinPoint joinPoint){Object oj = null;long start = System.currentTimeMillis();try {oj = joinPoint.proceed(); // 调用目标方法} catch (Throwable e) {throw new RuntimeException(e);}log.info(joinPoint.getSignature().toString()+"耗时:"+(System.currentTimeMillis()-start));return oj;}
}

可以看到不同的方法直接在 url 中进行更改重新运行界面即可获得:

 

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

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

相关文章

Stable Doodle:Stability AI推出的一款零门槛AI绘画神器

Stable Doodle是由Stability AI推出的一款零门槛AI绘画神器&#xff0c;可以将简单的草图转化为精美的图像。它可以将随手的塗鴉草稿转化为高畫質的完成圖&#xff0c;让用户能够以更快的速度将想法转化为精美的艺术作品。Stable Doodle利用最新的Stable Diffusion模型&#xf…

C#实现读写CSV文件的方法详解

目录 CSV文件标准 文件示例RFC 4180简化标准读写CSV文件 使用CsvHelper使用自定义方法总结 项目中经常遇到CSV文件的读写需求&#xff0c;其中的难点主要是CSV文件的解析。本文会介绍CsvHelper、TextFieldParser、正则表达式三种解析CSV文件的方法&#xff0c;顺带也会介绍一…

框架的知识点整理

目录 1、什么是Spring框架&#xff1f;Spring框架有哪些主要模块&#xff1f; 2 、 使用Spring框架有什么好处&#xff1f; 3、Spring MVC 工作原理 1、什么是Spring框架&#xff1f;Spring框架有哪些主要模块&#xff1f; Spring框架是一个开源的轻量级的Java应用程序开…

x86架构ubuntu22下运行WILL模拟器dophin

0. 环境 i5实体机ubuntu22 1. 安装依赖 $ sudo apt install build-essential git cmake ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libevdev-dev libusb-1.0-0-dev libxrandr-dev libxi-dev libpangocairo-1.0-0 qt6-base-private-dev libblueto…

腾讯云—自动挂载云盘

腾讯云&#xff0c;稍微麻烦了点。 腾讯云服务器&#xff0c;镜像为opencloudos 8。 ### 1、挂载云盘bash #首先通过以下命令&#xff0c;能够看到新的数据盘&#xff0c;如果不能需要通过腾讯云控制台卸载后&#xff0c;重新挂载&#xff0c;并重启服务器。 fdisk -l#为 /dev…

BES2700 SDK绝对时间获取方法

1 代码 2 实验 log 需要换算下

Tensorflow benchmark 实操指南

环境搭建篇见环境搭建-CentOS7下Nvidia Docker容器基于TensorFlow1.15测试GPU_东方狱兔的博客-CSDN博客 1. 下载Benchmarks源码 从 TensorFlow 的 Github 仓库上下载 TensorFlow Benchmarks&#xff0c;可以通过以下命令来下载 https://github.com/tensorflow/benchmarks 我…

【Unity2D】粒子特效

为什么要使用粒子特效 Unity 的粒子系统是一种非常强大的工具&#xff0c;可以用来创建各种各样的游戏特效&#xff0c;如火焰、烟雾、水流、爆炸等等。使用粒子特效可以使一些游戏动画更加真实或者使游戏效果更加丰富。 粒子特效的使用 在Hierarchy界面右键添加Effects->…

软件测试工程师的职业规划,你都做好了吗?

第一阶段&#xff1a;初级测试工程师 一般刚刚入门&#xff0c;熟悉基本的测试流程&#xff0c;此时已打基础为主。入门薪资一般在6000-8000元之间。 具体工作是&#xff1a; &#xff08;1&#xff09; 按照测试方案和流程对产品进行功能测试&#xff0c;检查产品是否有缺陷…

C#文件操作从入门到精通(2)——查看某个dll中有哪些函数

kernel32.dll中含有ini文件操作使用的函数,我们可以通过VisualStudio自带的dumpbin.exe查看dll所包含的函数,操作步骤如下: 1、找到dumpbin.exe所在的文件夹 我的电脑中安装了VisualStudio2019社区版以及VisualStudio2017Professional,但是我发现VisualStudio2019社区版中…

spring项目中idea提示Application context not configured for this file

今天在重构项目的时候&#xff0c;碰到一个问题。就是在spring底下&#xff0c;有一个包里面的所有配置类&#xff0c;在idea的开发工具类底下提示&#xff0c;Application context not configured for this file&#xff0c;如图所示 一开始以为是警告&#xff0c;不予处理&am…

MySQL中的函数

系列文章目录 MySQL常见的几种约束 文章目录 系列文章目录前言一、单行函数1.字符串函数 &#xff08;String StringBuilder&#xff09;2.数值函数 &#xff08;Math&#xff09;3.日期与时间函数4.流程函数&#xff08; IF SWITCH&#xff09;5.JSON函数6.其他函数 二、多行…

TreeMap的底层实现

0. 你需要知道的TreeMap的内置属性 0.1 节点属性 K key; // 键 V value; // 值 Entry<K,V> left; // 左子节点 Entry<K,V> right; // 右子节点 Entry<K,V> parent; // 父节点 boolean color; // 节点的颜色0.2 成员变量 //比较器对象private f…

rsync下行同步+inotify实时同步部署

目录 一、rsync简介 1.2 同步方式 1.2.1 全量备份 1.2.2 增量备份 1.2.3 差量备份 1.3 rsync的特点 1.4 rsync的优势与不足 1.5 rsync与cp、scp对比 1.6 rsync同类服务 二、rsync源服务器的关系 三、配置rsync源 3.1 基本思路 3.2 配置文件rsyncd.conf 3.3 独立…

HDFS Erasure coding-纠删码介绍和原理

HDFS Erasure coding-纠删码介绍和原理 三副本策略弊端Erasure Coding&#xff08;EC&#xff09;简介Reed- Solomon&#xff08;RS&#xff09;码 EC架构 三副本策略弊端 为了提供容错能力&#xff0c;hdfs回根据replication factor&#xff08;复制因子&#xff09;在不同的…

idea application.yml配置文件没有提示或读不到配置

1.首先确定你的resources文件夹正常且yml文件图表和下面一样 不一样的右键去设置 2.确保你已经缩进了且层级关系正常 3.如果以上都不是&#xff0c;先考虑删除.idea重开试试 4.以上解决不了就装以下两个插件解决

目标检测之3维合成

现在有一系列的图片&#xff0c;图片之间可以按照z轴方向进行排列。图片经过了目标检测&#xff0c;输出了一系列的检测框&#xff0c;现在的需求是将检测框按类别进行合成&#xff0c;以在3维上生成检测结果。 思路&#xff1a;将图片按照z轴方向排列&#xff0c;以z轴索引作…

微分流形2:流形上的矢量场和张量场

来了来了&#xff0c;切向量&#xff0c;切空间。流形上的所有的线性泛函的集合&#xff0c;注意是函数的集合。然后取流形上的某点p&#xff0c;它的切向量为&#xff0c;线性泛函到实数的映射。没错&#xff0c;是函数到实数的映射&#xff0c;是不是想到了求导。我们要逐渐熟…

Django模型将模型注释同步到数据库

1、安装django-comment-migrate库 pip install django-comment-migrate 2、将库注册到settings.py文件中 INSTALLED_APPS [...django_comment_migrate, # 表注释... ] 3、加注释 3.1、给模型&#xff08;表&#xff09;加注释 在模型的class Meta中编辑 verbose_name&…

UML/SysML建模工具更新(2023.7)(1-5)有国产工具

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 最近一段时间更新的工具有&#xff1a; 工具最新版本&#xff1a;Visual Paradigm 17.1 更新时间&#xff1a;2023年7月11日 工具简介 很用心的建模工具。支持编写用例规约。支持文本分析和C…