14. Spring AOP 的组成和实现

news2024/10/6 14:33:25

目录

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.coloradmin.cn/o/812432.html

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

相关文章

Zebec Card 将在亚洲、拉美等地区推出,生态全球化加速

随着以Visa、特斯拉、BNY Mellon、BlackRock、Mastercard、Gucci等为代表的传统商业机构巨头&#xff0c;以及萨尔瓦多、中非共和国等为代表的国家不断的向加密货币领域布局&#xff0c;越来越多的投资者开始以新的眼光来看待加密货币&#xff0c;仅在2022年&#xff0c;加密货…

1400*B. I Hate 1111(思维+数学)

Example input 3 33 144 69 output YES YES NO 题意&#xff1a; 问一个数字是否可以由 11&#xff0c;111&#xff0c;1111&#xff0c;11111...... 任意倍数加和所得。 解析&#xff1a; 可以观察到 1111%110&#xff0c;11111%1110&#xff0c;而后面更大的11111111…

C++部署学习

gcc -E src/main.c -o src/main.i gcc -S src/main.c -o src/main.s gcc -C src/main.c -o src/main.o gcc src/main.c -o exec ./exec

Vue2第八节 收集表单数据

&#xff08;1&#xff09;文本类型/密码类型 v-model收集的是value值 用户输入的就是value值 <input type"text" v-model"usrInfo.account"> <input type"password" v-model"usrInfo.password"> &#xff08;2&#xf…

Java程序逻辑控制的几个小练习(熟悉一下Java的语法)

虽然都是一些很简单的题目&#xff0c;但是都是很经典的编程题&#xff0c;多写几道可以熟悉一些Java代码&#xff0c; 之前C语言敲多了一时半会有点不适应&#xff0c;敲起来怪怪的&#xff0c;感觉手不是自己的了哈哈 目录 1. 根据年龄, 来打印出当前年龄的人是少年(低于18)…

多臂治疗规则的 Qini 曲线(Stefan Wager)

英文题目&#xff1a; Qini Curves for Multi-Armed Treatment Rules 中文题目&#xff1a;多臂治疗规则的 Qini 曲线 单位&#xff1a;Stefan Wager 论文链接&#xff1a; 代码&#xff1a;GitHub - grf-labs/maq: Treatment rule evaluation via the multi-armed Qini …

软件测试面试【证券项目公司】

这家公司是做证券项目的&#xff0c;约的9点钟&#xff0c;路程还是有点遥远&#xff0c;转了一趟公交两趟地铁&#xff0c;精力都花在了路上&#xff0c;感觉有点累&#xff0c;以下是今天得面试流程。 到公司前台给我了一张面试表&#xff0c;写完之后就是等待面试。一共面试…

Python模块psycopg2连接postgresql

目录 1. 基础语法 2. 基础用法 3. 多条SQL 4. 事务SQL 1. 基础语法 语法 psycopg2.connect(dsn #指定连接参数。可以使用参数形式或 DSN 形式指定。host #指定连接数据库的主机名。dbname #指定数据库名。user #指定连接数据库使用的用户名。…

TMS XData v5.11 2023 crack,全功能查询机制

TMS XData v5.11 2023 crack,全功能查询机制 用于多层REST/JON-HTTP/HTTPS应用服务器开发和ORM远程处理的Delphi框架。 功能概述 基于REST/JONS架构风格的服务器 从不同的客户端平台(如.NET、Java、jаvascript)轻松访问&#xff0c;因为它是基于REST/JSON的 使用标准POST、GET…

【RabbitMQ】golang客户端教程2——工作队列

任务队列/工作队列 在上一个教程中&#xff0c;我们编写程序从命名的队列发送和接收消息。在这一节中&#xff0c;我们将创建一个工作队列&#xff0c;该队列将用于在多个工人之间分配耗时的任务。 工作队列&#xff08;又称任务队列&#xff09;的主要思想是避免立即执行某些…

[golang gin框架] 43.Gin商城项目-微服务实战之后台Rbac微服务之管理员的增删改查以及管理员和角色关联

上一节讲解了后台Rbac微服务角色增删改查微服务,这里讲解权限管理Rbac微服务管理员的增删改查微服务以及管理员和角色关联微服务功能 一.实现后台权限管理Rbac之管理员增删改查微服务服务端功能 1.创建Manager模型 要实现管理员的增删改查,就需要创建对应的模型,故在server/r…

使用Beego和MySQL实现帖子和评论的应用,并进行接口测试(附源码和代码深度剖析)

文章目录 小项目介绍源码分析main.gorouter.gomodels/user.gomodels/Post.gomodels/comment.gocontrollers/post.gocontrollers/comment.go 接口测试测试增加帖子测试查看帖子测试增加评论测试查看评论 小项目介绍 经过对需求的分析&#xff0c;我增加了一些额外的东西&#x…

Linux学习之脚本优先级控制

fork炸弹 在编写Shell脚本时不要写出不可控的死循环&#xff0c;比如func() { func | func& } ; func&#xff0c;简写版为.(){ .|.& };.。接下来见证一下这两条语句的威力。因为在root用户下许多资源没有限制&#xff0c;所以useradd userfork新建一个用户userfork&a…

fwrite函数

1、函数声明 size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream ); 2、参数说明 buffer 指向要写入的数据的指针。 size 项大小&#xff08;以字节为单位&#xff09;。 count 要写入的项的最大数量。 stream 指向 FILE 结构的指针。 3、…

OpenCloudOS 与PolarDB全面适配

近日&#xff0c;OpenCloudOS 开源社区签署阿里巴巴开源 CLA (Contribution License Agreement, 贡献许可协议), 正式与阿里云 PolarDB 开源数据库社区牵手&#xff0c;并展开 OpenCloudOS &#xff08;V8&#xff09;与阿里云开源云原生数据库 PolarDB 分布式版、开源云原生数…

AD21原理图的高级应用(四)线束的设计及应用

&#xff08;四&#xff09;线束的设计及应用 Altium Designer 21 可以使用 Signal Harnesses(信号线束)的方法来建立元件之间的连接,也可用于不同原理图间的信号对接。信号线束是一种抽象连接,操作方式类似于总线,但信号线束可对包括总线、导线和其他信号线束在内的不同信号进…

el-button增加下载功能

vue3和element-plus <el-uploadv-model:file-list="fileList"action="/api/upload"multiple:limit="1":headers="headers" ><el-button type="primary">选择文件</el-button><template #file

【前缀和】560.和为 K 的子数组

Halo&#xff0c;这里是Ppeua。平时主要更新C&#xff0c;数据结构算法&#xff0c;Linux与ROS…感兴趣就关注我bua&#xff01; 和为K的子数组 题目:示例:题解&#xff1a;解法一:解法二: 题目: 示例: 题解&#xff1a; 解法一: 暴力解法:我们很容易想到通过两个for循环去遍…

【使用 DSP 滤波器加速速度和位移】使用信号处理算法过滤加速度数据并将其转换为速度和位移研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

操作系统理论:Linux进程与进程状态(进程调度的大O(1)算法数据结构模型)

文章目录 一.进程的基本概念进程间的基本关系:父子关系 二.进程状态(1)进程的运行状态RLinux进程调度的大O(1)算法数据结构模型(运行队列哈希桶):进程的运行时间片 (2)进程的睡眠状态(S和D)(3)进程的僵尸状态和死亡状态 一.进程的基本概念 冯诺依曼体系的计算机在运行时,内存中…