【SSM详细教程】-14-SpringAop超详细讲解

news/2024/10/30 7:43:57/

 精品专题:

01.《C语言从不挂科到高绩点》课程详细笔记

https://blog.csdn.net/yueyehuguang/category_12753294.html?spm=1001.2014.3001.5482

02. 《SpringBoot详细教程》课程详细笔记

https://blog.csdn.net/yueyehuguang/category_12789841.html?spm=1001.2014.3001.5482

03.《SpringBoot电脑商城项目》课程详细笔记

https://blog.csdn.net/yueyehuguang/category_12752883.html?spm=1001.2014.3001.5482

04.《VUE3.0 核心教程》课程详细笔记 

https://blog.csdn.net/yueyehuguang/category_12769996.html?spm=1001.2014.3001.5482

05. 《SSM详细教程》课程详细笔记 

https://blog.csdn.net/yueyehuguang/category_12806942.html?spm=1001.2014.3001.5482

================================

||     持续分享系列教程,关注一下不迷路 ||

||                视频教程:墨轩大楼               ||

================================

📚 AOP 概念及优点

AOP为Aspect Oriented Programming的缩写,被称为面向切面编程。

AOP 主要用于处理共通逻辑,例如:记录日志、性能统计、安全控制、事务处理、异常处理等等。AOP可以将这些共通的逻辑从普通业务逻辑代码中分离出来,这样在日后修改这些逻辑的时候,就不会影响普通业务逻辑的代码。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。

AOP 、OOP在名字上虽然非常类似,但却是面向不同领域的两种设计思想。OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象,以获得更加清晰高效的逻辑单元划分。AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

AOP 需要以 OOP为前提和基础。

🌾 什么是方面

面向切面编程,我们首先要知道的一个概念就是方面,也就是把什么东西给隔离出来。方面是指封装处理共通业务的组件,该组件被作用到其他目标组件方法上。

🌾 什么是目标

目标是指被一个或多个方面所作用的对象。

🌾 什么是切入点

切入点是用于指定哪些组件和方法使用方面功能,在Spring中利用一个表达式指定切入目标。

Spring提供了以下常用的切入点表达式:

  • 方法限定表达式

execution(修饰符?返回类型 方法名(参数) throws 异常类型?)

  • 类型限定表达式

within(包名.类型)

  • Bean 名称限定表达式

bean("Bean的id或name属性值")

🌾 什么是通知

通知是用于指定方面组件和目标组件作用的时机,例如方面功能在目标方法之前或之后执行等时机。

Spring框架提供以下几种类型的通知:

  • 前置通知:先执行方面功能在执行目标功能
  • 后置通知:先执行目标功能再执行方面功能(目标无异常才执行方面)
  • 最终通知:先执行目标功能再执行方面功能(目标有无异常都执行方面)
  • 异常通知:先执行目标,抛出后执行方面
  • 环绕通知:先执行方面前置部分,然后执行目标,最后再执行方面后置部分。

Spring框架提供5种通知,可以按照下面的try-catch-finally结构理解。

try{// 前置通知--执行方面// 环绕通知--前置部分// 执行目标组件方法// 环绕通知--后置部分// 后置通知--执行方面
}catch{// 异常通知--执行方面
}finally{// 最终通知--执行方面
}

🌾 AOP 实现原理

Spring AOP 实现主要是基于动态代理技术。当Spring采用AOP配置后,Spring容器返回的目标对象,实质上是Spring利用动态代理技术生成的一个代理类型。代理类重写了原目标组件方法的功能,在代理类种调用方面对象功能和目标对象功能。

Spring框架采用了两种动态代理实现:

  • 利用cglib工具包:目标没有接口时采用此方法,代理类是利用继承方法生成一个目标子类。
  • 利用JDK Proxy API:目标有接口时采用此方法,代理类是采用实现目标接口方法生成一个类。

📚 AOP 开发案例

🌾 AOP 前置通知案例

👉 需求:使用Spring AOP 前置通知,在访问Controller中每个方法前,记录用户的操作日志。

👉 步骤:

  • 创建方面组件
  • 声明方面组件
  • 将方面组件作用到目标组件上

🍒 导入依赖

我们基于前面SpringMVC的基础上去添加AOP功能,所以在前面SpringMVC的环境基础上我们需要追加AOP的依赖。


<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.8</version>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.10</version>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.10</version>
</dependency>

🍒 创建Controller

创建一个AOPTestContrller,模拟查询用户数据的Controller,代码如下:

@Controller
public class AOPTestController {@RequestMapping("/find")@ResponseBodypublic String findUser(){// 模拟查询用户数据System.out.println("--》 查询用户数据");return "查询了用户数据";}
}
🍒 创建方面组件

创建方面组件OperateLogger,并在该类中创建记录用户操作日志的方法,代码如下:

package com.moxuan.mvc_study.config;import org.aspectj.lang.ProceedingJoinPoint;import java.text.SimpleDateFormat;
import java.util.Date;/*** 用于记录日志的方面组件,演示Spring AOP 的各种通知类型*/
public class OperateLogger {/*** 前置通知、后置通知、最终通知使用的方法*/public void logUser(JoinPoint p){// 目标组件的类名String className = p.getTarget().getClass().getName();// 调用的方法名String method = p.getSignature().getName();// 当前系统时间String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());// 拼日志信息String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";// 记录日志System.out.println(msg);}
}
🍒 声明方面组件

springmvc.xml中,声明该方面组件,关键代码如下:

<bean id="operateLogger" class="com.moxuan.mvc_study.config.OperateLogger"></bean>
🍒 将方面组件作用到目标组件上

springmvc.xml中,将声明的方面组件作用到controller包下面所有类的所有方法上,关键代码如下:

<aop:config><aop:aspect ref="operateLogger"><!--配置方面组件,作用到的目标方法,pointcut 方面组件的切入点--><aop:before method="logUser"pointcut="within(com.moxuan.mvc_study.controller..*)" /></aop:aspect>
</aop:config>

🍒 测试效果

发送请求:http://localhost:8080/find

可以看到,当配置<aop:before> 前置通知后,方面组件会在执行目标组件的方法时自动触发执行。

🌾 AOP 环绕通知案例

🍒 创建方面组件

依赖和控制器我们延用前置通知案例中的,我们来修改一下方面组件:

public Object logUserRound(ProceedingJoinPoint p) throws Throwable{// 目标组件的类名String className = p.getTarget().getClass().getName();// 调用的方法名String method = p.getSignature().getName();// 当前系统时间String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());// 拼日志信息String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";// 记录日志System.out.println(msg);// 执行目标组件的方法Object obj = p.proceed();//在调用目标组件业务方法后也可以做一些业务处理System.out.println("---> 已经执行完毕了组件业务了....");return obj;}

🍒 配置环绕通知

组件声明我们前面已经做过了,这里我们直接配置一下环绕通知:

    <aop:config><aop:aspect ref="operateLogger"><!--配置方面组件,作用到的目标方法,pointcut 方面组件的切入点--><aop:around method="logUserRound"pointcut="within(com.moxuan.mvc_study.controller..*)"/></aop:aspect></aop:config>

🍒 测试效果

请求地址:http://localhost:8080/find

可以看到,方面组件中的前置部分会在方法执行前执行,方法执行完毕之后执行后置部分。

🌾 AOP 异常通知案例

需求:使用AOP异常通知,在每个Controller的方法发生异常时,记录异常日志。

🍒 编写方面组件
/*** 异常通知使用方法* @param e*/
public void logException(Exception e){StackTraceElement[] elements = e.getStackTrace();// 将异常信息记录System.out.println("--》"+elements[0].toString());
}
🍒 配置异常通知

将异常通知方面组件作用到目标组件上

    <aop:config><aop:aspect ref="operateLogger"><aop:after-throwing method="logException" throwing="e"pointcut="within(com.moxuan.mvc_study.controller..*)"/></aop:aspect></aop:config>
🍒 编写目标组件
@RequestMapping("/find")
@ResponseBody
public String findUser(){// 模拟查询用户数据System.out.println("目标组件:--》 查询用户数据");// 制造一个异常,便于测试异常通知Integer.valueOf("abc");return "查询了用户数据";
}
🍒 测试效果

发送请求:http://localhost:8080/find

🌾 AOP 注解使用案例

👉需求: 使用Spring AOP 注解替代XML配置,重构上面三个案例

👉方案:

  • @Aspect : 用于声明方面组件
  • @Before:用于声明前置通知
  • @AfterReturning:用于声明后置通知
  • @After:用于声明最终通知
  • @Around:用于声明环绕通知
  • @AfterThrowing:用于声明异常通知

🍒 开启AOP注解扫描

springmvc.xml中,去掉方面组件声明以及作用的xml配置,并开启AOP注解扫描,关键代码如下:

<!-- 开启AOP注解扫描-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
🍒 使用注解声明方面组件

在OperateLogger中使用@Aspect注解声明方面组件,并分别用@Before、@Around、@AfterThrowing注解声明三个方法,将方面组件作用到目标组件上,代码如下:

package com.moxuan.mvc_study.config;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** 用于记录日志的方面组件,演示Spring AOP 的各种通知类型*/
@Component
@Aspect
public class OperateLogger {/*** 前置通知、后置通知、最终通知使用的方法*/@Before("within(com.moxuan.mvc_study.controller..*)")public void logUser(JoinPoint p){System.out.println("^^^^^进入到了前置通知^^^^^^");// 目标组件的类名String className = p.getTarget().getClass().getName();// 调用的方法名String method = p.getSignature().getName();// 当前系统时间String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());// 拼日志信息String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";// 记录日志System.out.println(msg);System.out.println("^^^^^前置通知结束^^^^^^");}@Around("within(com.moxuan.mvc_study.controller..*)")public Object logUserRound(ProceedingJoinPoint p) throws Throwable{System.out.println("^^^^^进入环绕通知^^^^^^");// 目标组件的类名String className = p.getTarget().getClass().getName();// 调用的方法名String method = p.getSignature().getName();// 当前系统时间String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());// 拼日志信息String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";// 记录日志System.out.println(msg);// 执行目标组件的方法Object obj = p.proceed();//在调用目标组件业务方法后也可以做一些业务处理System.out.println("---> 已经执行完毕了组件业务了....");System.out.println("^^^^^环绕通知结束^^^^^^");return obj;}/*** 异常通知使用方法* @param e*/@AfterThrowing(pointcut = "within(com.moxuan.mvc_study.controller..*)",throwing ="e")public void logException(Exception e){System.out.println("^^^^^进入异常通知^^^^^^");StackTraceElement[] elements = e.getStackTrace();// 将异常信息记录System.out.println("--》"+elements[0].toString());System.out.println("^^^^^异常通知结束^^^^^^");}
}
🍒 测试效果

请求路径:http://localhost:8080/find

从结果可以看到,当发生异常之后,环绕通知后置部分将不会执行。


http://www.ppmy.cn/news/1543043.html

相关文章

手机备忘录怎么导出到电脑,

在忙碌的现代生活中&#xff0c;我们常常需要在手机和电脑之间切换工作&#xff0c;手机里的备忘录记录了我们的重要事项&#xff0c;有时候需要在电脑端查看和处理。那么&#xff0c;如何将手机备忘录的内容导出到电脑呢&#xff1f;其实&#xff0c;这个问题的解决方法并不复…

踩坑:关于使用ceph pg repair引发的业务阻塞

概述 在某次故障回溯中&#xff0c;发现引发集群故障&#xff0c;slow io&#xff0c;pg stuck的罪魁祸首竟是做了一次ceph pg repair $pgid。然而ceph pg repair作为使用频率极高的&#xff0c;用来修复pg不一致的常用手段&#xff0c;平时可能很少注意其使用规范和可能带来的…

Uniapp使用UviewPlus在APP当中进行文件上传的解决方案

Uniapp使用UviewPlus在APP当中进行文件上传的解决方案 吐槽&#xff1a;真的可以不用就不要用uniapp&#xff0c;就像s一样&#xff0c;可以的话上Recat好很多&#xff0c;踩了很多坑。 原因 如果你是axios的忠实奴隶那么你会遇到第一个坑&#xff0c;就是uniapp下的原生编译不…

Rust 程序设计语言学习——高级特性

RUST 中常用部分学习结束之后&#xff0c;我们来接触一些 RUST 中的其他高级用法。 不安全 Rust&#xff1a;用于当需要舍弃 Rust 的某些保证并负责手动维持这些保证高级 trait&#xff1a;与 trait 相关的关联类型&#xff0c;默认类型参数&#xff0c;完全限定语法&#xff…

【C语言】预处理(预编译)详解(下)(C语言最终篇)

文章目录 一、#和##1.#运算符2.##运算符 二、预处理指令#undef三、条件编译1.单分支条件编译2.多分支条件编译3.判断符号是否被定义4.判断符号是否没有被定义 四、头文件的包含1.库头文件的包含2.本地头文件的包含3.嵌套包含头文件的解决方法使用条件编译指令使用预处理指令#pr…

分布式 ID 生成策略(二)

在上一篇文章&#xff0c;分布式 ID 生成策略&#xff08;一&#xff09;&#xff0c;我们讨论了基于数据库的 ID 池策略&#xff0c;今天来看另一种实现&#xff0c;基于雪花算法的分布式 ID 生成策略。 如图所示&#xff0c;我们用 41 位时间戳 12 位机器 ID 10 位序列号&a…

使用 Kibana 将地理空间数据导入 Elasticsearch 以供 ES|QL 使用

作者&#xff1a;来自 Elastic Craig Taverner 如何使用 Kibana 和 csv 采集处理器将地理空间数据采集到 Elasticsearch 中&#xff0c;以便在 Elasticsearch 查询语言 (ES|QL) 中进行搜索。Elasticsearch 具有强大的地理空间搜索功能&#xff0c;现在 ES|QL 也具备这些功能&am…

Python 网络爬虫快速入门

网络爬虫是一种自动化的程序&#xff0c;用于从互联网上抓取数据。Python 由于其简洁的语法和丰富的库支持&#xff0c;成为编写网络爬虫的理想选择。本文将带你快速入门 Python 网络爬虫&#xff0c;从安装必要的库到编写一个简单的爬虫&#xff0c;再到处理更复杂的情况。 1…