详解Spring AOP(一)

目录

1. AOP概述

2.Spring AOP快速入门

2.1引入AOP依赖

2.2编写AOP程序

 3.Spring AOP核心概念

3.1切点(PointCut)

3.2连接点(Join Point)

3.3通知(Advice)

3.4切面(Aspect)

4.通知类型

 5.@PointCut

 6.切面优先级 @Order


1. AOP概述

AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它旨在通过预编译方式和运行期间动态代理实现程序功能的统一维护。

AOP定义

  1. AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点技术。
  2. 它是Spring框架中的一个重要内容,也是函数式编程的一种衍生范型。
  3. AOP通过“切面”对业务逻辑的各个部分进行隔离,降低业务逻辑之间的耦合度,提高程序的可重用性和开发效率。

 与OOP的区别:

OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。

AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

应用场景:记录日志、性能监控、权限控制、缓存优化、事务管理(如声明式事务)

案例分析:

我们现在有⼀个项⽬ , 项⽬中开发了很多的业务功能

现在有⼀些业务的执行效率比较低, 耗时较长, 我们需要对接口进行优化.

第⼀步就需要定位出执行耗时比较长的业务方法, 再针对该业务方法来进行优化

何定位呢? 我们就需要统计当前项目中每⼀个业务方法的执行耗时.

如何统计呢? 可以在业务方法运行前和运行后, 记录下方法的开始时间和结束时间, 两者之差就是这个方法的耗时.

这种方法是可以解决问题的, 但⼀个项⽬中会包含很多业务模块, 每个业务模块又有很多接口 , ⼀个接口又包含很多方法, 如果我们要在每个业务方法中都记录方法的耗时, 会增加特别多的工作.

AOP就可以做到在不改动这些原始方法的基础上, 针对特定的方法进⾏功能的增强.

AOP的作用:在程序运行期间在不修改源代码的基础对已有方法进行增强(无侵⼊性: 解耦)

2.Spring AOP快速入门

需求:统计系统各个方法的zhixing

2.1引入AOP依赖

pom.xml文件中添加配置

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

2.2编写AOP程序

记录Controller中每个方法的执行时间

 @Slf4j@Aspect@Componentpublic class TimeAspect {/*** 记录⽅法耗时 */@Around("execution(* com.example.demo.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { //记录⽅法执⾏开始时间long begin = System.currentTimeMillis(); //执⾏原始⽅法Object result = pjp.proceed(); //记录⽅法执⾏结束时间long end = System.currentTimeMillis(); //记录⽅法执⾏耗时log.info(pjp.getSignature() + "执⾏耗时 : {}ms", end - begin);return result;}}

运行程序, 观察日志

 对代码进行简单分析:

1.@Aspect:标识这是一个切面类
2.@Around:环绕通知,在目标方法的前后都会被执行.后面的表达式表示对哪些方法进行增强
3.ProceedingJoinPoint.proceed()让原始方法执行

整个代码划分为三部分 

我们通过AOP入门程序完成了业务接口执行耗时的统计.

通过上面的程序,我们也可以感受到AOP面向切面编程的一些优势:

  • 代码无侵入:不修改原始的业务方法,就可以对其进行了功能的增强或者是功能的改变
  • 减少了重复代码
  • 提高开发效率
  • 维护方便

 3.Spring AOP核心概念

3.1切点(PointCut)

切点(Pointcut),也称之为"切入点"
切点的作用就是提供一组规则(使用AspectJ pointcut expression language来描述),告诉程序对哪些方法来进行功能增强.

上面的表达式execution(* com.example.demo.controller.*.*(..))就是切点表达式. 

3.2连接点(Join Point)

满足切点表达式规则的方法,就是连接点.也就是可以被AOP控制的方法以入门程序举例,所有com.example.demo.controller路径下的方法,都是连接点.

 package com.example.demo.controller; @RequestMapping("/book") @RestControllerpublic class BookController { @RequestMapping("/addBook")public Result addBook(BookInfo bookInfo) {//...代码省略} @RequestMapping("/queryBookById")public BookInfo queryBookById(Integer bookId){//...代码省略} @RequestMapping("/updateBook")public Result updateBook(BookInfo bookInfo) {//...代码省略}}

上述BookController中的方法都是连接点

切点和连接点的关系

连接点是满足切点表达式的元素.切点可以看做是保存了众多连接点的一个集合

比如:

切点表达式:学校全体教师

连接点就是:张三,李四等各个老师

3.3通知(Advice)

通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)比如上述程序中记录业务方法的耗时时间,就是通知.

在AOP面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容.

3.4切面(Aspect)

切面(Aspect)=切点(Pointcut)+通知(Advice)

通过切面就能够描述当前AOP程序需要针对于哪些方法,在什么时候执行什么样的操作.

切面既包含了通知逻辑的定义,也包括了连接点的定义.

 切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)

4.通知类型

上面我们讲了什么是通知,接下来学习通知的类型.

Spring中AOP的通知类型有以下几种:

@Around:环绕通知,此注解标注的通知方法在目标方法前,后都被执行
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不执行
@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

 通过代码来测试这几个通知:

import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Slf4j@Aspect@Componentpublic class AspectDemo { //前置通知@Before("execution(* com.example.demo.controller.*.*(..))")public void doBefore() {log.info("执⾏  Before ⽅法");} //后置通知@After("execution(* com.example.demo.controller.*.*(..))")public void doAfter() {log.info("执⾏  After ⽅法");} //返回后通知@AfterReturning("execution(* com.example.demo.controller.*.*(..))")public void doAfterReturning() {log.info("执⾏  AfterReturning ⽅法");} //抛出异常后通知@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")public void doAfterThrowing() {log.info("执⾏  doAfterThrowing ⽅法");} //添加环绕通知@Around("execution(* com.example.demo.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around ⽅法开始执⾏");Object result = joinPoint.proceed();log.info("Around ⽅法结束执⾏");return result;}}

编写测试程序:

 package com.example.demo.controller; import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RequestMapping("/test") @RestControllerpublic class TestController { @RequestMapping("/t1")public String t1() {return "t1";} @RequestMapping("/t2")public boolean t2() {int a = 10 / 0;return true;}}

运行程序,观察日志:
1.正常运行的情况

http://127.0.0.1:8080/test/t1

观察日志

程序正常运行的情况下,@AfterThrowing标识的通知方法不会执行
从上图可以看出,@Around标识的通知方法包含两部分,一个"前置逻辑",一个"后置逻辑",其中"前置逻辑"会先于@Before标识的通知方法执行,"后置逻辑"会晚于@After标识的通知方法执行

2.异常时的情况

http://127.0.0.1:8080/test/t2

观察日志: 

程序发生异常的情况下:
@AfterReturning标识的通知方法不会执行@AfterThrowing标识的通知方法执行了
@Around环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在执行

注意事项:
@Around环绕通知需要调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行.
@Around环绕通知方法的返回值,必须指定为Object来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的.
一个切面类可以有多个切点.

 5.@PointCut


上面代码存在一个问题,就是存在大量重复的切点表达式execution(* com.example.demo.controller.*.*(..)),

Spring提供@PointCut注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可.
上述代码就可以修改为:

 @Slf4j@Aspect@Componentpublic class AspectDemo {//定义切点(公共的切点表达式)@Pointcut("execution(* com.example.demo.controller.*.*(..))")private void pt(){}//前置通知@Before("pt()")public void doBefore() {//...代码省略} //后置通知@After("pt()")public void doAfter() {//...代码省略} //返回后通知@AfterReturning("pt()")public void doAfterReturning() {//...代码省略} //抛出异常后通知@AfterThrowing("pt()")public void doAfterThrowing() {//...代码省略} //添加环绕通知     @Around("pt()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {//...代码省略}}

注意:

当切点定义使用private修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把private改为public.

引用方式为:全限定类名.方法名()

 @Slf4j@Aspect@Componentpublic class AspectDemo2 { //前置通知@Before("com.example.demo.aspect.AspectDemo.pt()")public void doBefore() {log.info("执⾏  AspectDemo2 -> Before ⽅法");}}

 6.切面优先级 @Order

当我们在一个项目中,定义了多个切面类并且这些切面类的多个切入点都匹配到了同一个目标方法

当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?
我们还是通过程序来求证:

定义多个切面类:

为简单化,只写@Before和aAfter两个通知:

 @Componentpublic class AspectDemo1 {@Pointcut("execution(* com.example.demo.controller.*.*(..))")private void pt(){}//前置通知@Before("pt()")public void doBefore() {log.info("执⾏  AspectDemo1 -> Before ⽅法");} //后置通知@After("pt()")public void doAfter() {log.info("执⾏  AspectDemo1 -> After ⽅法");}}
 @Componentpublic class AspectDemo2 {@Pointcut("execution(* com.example.demo.controller.*.*(..))")private void pt(){}//前置通知@Before("pt()")public void doBefore() {log.info("执⾏  AspectDemo2 -> Before ⽅法");} //后置通知@After("pt()")public void doAfter() {log.info("执⾏  AspectDemo2 -> After ⽅法");}}
 @Componentpublic class AspectDemo3 {@Pointcut("execution(* com.example.demo.controller.*.*(..))")private void pt(){}//前置通知@Before("pt()")public void doBefore() {log.info("执⾏  AspectDemo3 -> Before ⽅法");} //后置通知@After("pt()")public void doAfter() {log.info("执⾏  AspectDemo3 -> After ⽅法");}}

运行程序,访问接口:

http://127.0.0.1:8080/test/t1

观察日志:
 

通过上述程序的运行结果,可以看出:
存在多个切面类时,默认按照切面类的类名字母排序:
@Before通知:字母排名靠前的先执行
@After通知:字母排名靠前的后执行
但这种方式不方便管理,我们的类名更多还是具备一定含义的.
Spring给我们提供了一个新的注解来控制这些切面通知的执行顺序:@Order

使用方式如下:
 

 @Aspect@Component@Order(2)public class AspectDemo1 {//...代码省略 }@Aspect@Component@Order(1)public class AspectDemo2 {//...代码省略 }@Aspect@Component@Order(3)public class AspectDemo3 {//...代码省略 }

重新运行程序,访问接口:

http://127.0.0.1:8080/test/t1

观察日志:

通过上述程序的运行结果,得出结论:
@Order注解标识的切面类,执行顺序如下:
@Before通知:数字越小先执行
@After通知:数字越大先执行
@Order控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法。

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

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

相关文章

时间?空间?复杂度??

1.什么是时间复杂度和空间复杂度&#xff1f; 1.1算法效率 算法效率分析分为两种&#xff1a;第一种是时间效率&#xff0c;第二种是空间效率。时间效率被称为时间复杂度&#xff0c;而空间效率被称为空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空…

CentOS 8.5 - 配置ssh的免密登录

文章目录 生成ssh密钥公钥内容放入服务器 生成ssh密钥 在本地主机安装 ssh工具&#xff0c;并生成公钥、私钥。 # 命令行输入 ssh-keygen -r rsa# 会在当前用户的家目录下生成一个.ssh目录公钥内容放入服务器 将上一步生成的id_rsa.pub公钥的内容复制到远程服务器 # 编辑文…

Day17--654.最大二叉树+617.合并二叉树+700.二叉搜索树中的搜索+ 98.验证二叉搜索树

一、654.最大二叉树 题目链接&#xff1a;https://leetcode.cn/problems/maximum-binary-tree/ 文章讲解&#xff1a;https://programmercarl.com/0654.%E6%9C%80%E5%A4%A7%E4%BA%8C%E5%8F%89%E6%A0%91.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1MG411G7ox…

SpringMVC系列四: Rest-优雅的url请求风格

Rest请求 &#x1f49e;Rest基本介绍&#x1f49e;Rest风格的url-完成增删改查需求说明代码实现HiddenHttpMethodFilter机制注意事项和细节 &#x1f49e;课后作业 上一讲, 我们学习的是SpringMVC系列三: Postman(接口测试工具) 现在打开springmvc项目 &#x1f49e;Rest基本介…

Part 5.2 KMP

KMP 算法可以用来解决模式串匹配问题。 【模板】KMP 题目描述 给出两个字符串 s 1 s_1 s1​ 和 s 2 s_2 s2​&#xff0c;若 s 1 s_1 s1​ 的区间 [ l , r ] [l, r] [l,r] 子串与 s 2 s_2 s2​ 完全相同&#xff0c;则称 s 2 s_2 s2​ 在 s 1 s_1 s1​ 中出现了&…

「动态规划」如何求最长递增子序列的长度?

300. 最长递增子序列https://leetcode.cn/problems/longest-increasing-subsequence/description/ 给你一个整数数组nums&#xff0c;找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其…

使用Apache Flink实现实时数据同步与清洗:MySQL和Oracle到目标MySQL的ETL流程

使用Apache Flink实现实时数据同步与清洗&#xff1a;MySQL和Oracle到目标MySQL的ETL流程 实现数据同步的ETL&#xff08;抽取、转换、加载&#xff09;过程通常涉及从源系统&#xff08;如数据库、消息队列或文件&#xff09;中抽取数据&#xff0c;进行必要的转换&#xff0…

2024最新版Node.js下载安装及环境配置教程(非常详细)

一、进入官网地址下载安装包 官网&#xff1a;Node.js — Run JavaScript Everywhere 其他版本下载&#xff1a;Node.js — Download Node.js (nodejs.org) 选择对应你系统的Node.js版本 二、安装程序 &#xff08;1&#xff09;下载完成后&#xff0c;双击安装包&#xf…

Go的GUI Fyne开发环境搭建—Windows 11

安装go 到官网下载安装go安装包 https://go.dev/learn/ 通过如下命令检验安装是否成功&#xff0c;出现版本号则安装成功 go version安装国内go依赖包代理 go env -w GOPROXYhttps://goproxy.cn安装gcc编译器 直接用官网提供的安装建议第二条&#xff0c;到这个地址进行下载…

二刷算法训练营Day41 (Day40休息) | 动态规划(3/17)

目录 详细布置&#xff1a; 1. 背包问题理论基础 1.1 01背包 2. 46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; 一维dp数组&#xff08;滚动数组&#xff09; 3. 416. 分割等和子集 详细布置&#xff1a; 1. 背包问题理论基础 但说实话&#xff0c;背包九讲…

C#开发-集合使用和技巧(六)特殊转换方法SelectMany的介绍和用法

C#开发-集合使用和技巧&#xff08;六&#xff09; 特殊转换方法SelectMany的介绍和用法 介绍使用示例Select与SelectMany对比特殊情况 介绍 SelectMany 方法在C#中用于将集合中的元素转换为其他类型的集合&#xff0c;并将这些集合扁平化为一个单一的序列。它是LINQ的一部分…

Unity URP下通过相机让部分Render不受后处理渲染

我们有时候不想某些对象受到后处理影响&#xff0c;找到了这样一个决绝办法&#xff0c;通过增加一个Overlay相机只照射这个模型来实现&#xff0c;下面看看如何实现。 第一步 首先我们拖一个测试场景&#xff0c;有如下一些元素 一个盒子&#xff0c;以后后处理&#xff0c…

Python武器库开发-武器库篇之ThinkPHP6 多语言本地文件包含漏洞(六十七)

Python武器库开发-武器库篇之ThinkPHP6 多语言本地文件包含漏洞&#xff08;六十七&#xff09; 漏洞环境搭建 这里我们使用Kali虚拟机安装docker并搭建vulhub靶场来进行ThinkPHP漏洞环境的安装&#xff0c;我们进入 ThinkPHP漏洞环境&#xff0c;可以 cd ThinkPHP&#xff0…

【Solr 学习笔记】Solr 源码启动教程

Solr 源码启动教程 本教程记录了如何通过 IDEA 启动并调试 Solr 源码&#xff0c;从 Solr9 开始 Solr 项目已由 ant 方式改成了 gradle 构建方式&#xff0c;本教程将以 Solr 9 为例进行演示&#xff0c;IDE 选择使用 IntelliJ IDEA。 Solr github 地址&#xff1a;https://gi…

【机器学习】机器学习重要方法——深度学习:理论、算法与实践

文章目录 引言第一章 深度学习的基本概念1.1 什么是深度学习1.2 深度学习的历史发展1.3 深度学习的关键组成部分 第二章 深度学习的核心算法2.1 反向传播算法2.2 卷积神经网络&#xff08;CNN&#xff09;2.3 循环神经网络&#xff08;RNN&#xff09; 第三章 深度学习的应用实…

AI交互及爬虫【数据分析】

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

区块链实验室(37) - 交叉编译百度xuperchain for arm64

纠结了很久&#xff0c;终于成功编译xuperchain for arm64。踩到1个坑&#xff0c;说明如下。 1、官方文档是这么说的&#xff1a;go语言版本推荐1.5-1.8 2、但是同一个页面&#xff0c;又是这么说的&#xff1a;不推荐使用1.11之前的版本。 3、问题来了&#xff1a;用什么版本…

2024年特种设备(门式起重机司机)考试真题题库。

181."ZZ"表示钢丝绳为&#xff08; &#xff09;。 A.右同向捻 B.左同向捻 C.右交互捻 D.左交互捻 答案:A 182.桥式起重机的金属结构主要由起重机桥架&#xff08;又称大车桥架&#xff09;、&#xff08; &#xff09;和操纵室&#xff08;司机室&#xff09;…

提升工作效率的实体和虚拟工具推荐

在现代工作中&#xff0c;我们常常需要利用各种工具来提高工作效率。本文将介绍一款实体工具和一款虚拟工具&#xff0c;它们都能够有效地提升工作效率&#xff0c;让我们更高效地完成任务。 实体工具&#xff1a;金鸣表格文字识别大师 金鸣表格文字识别大师是一款优秀的文字识…

Day 32:503. 下一个更大的元素Ⅱ

Leetcode 503. 下一个更大的元素Ⅱ 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个比它…