13. Spring AOP(一)思想及使用

news2024/11/18 14:00:02

1. 什么是Spring AOP

AOP的全称是Aspect Oriented Programming,也就是面向切面编程,是一种思想。它是针对OOP(面向对象编程)的一种补充,是对某一类事情的集中处理。比如一个博客网站的登陆验证功能,在用户进行新增、编辑、删除博客等操作前都需要进行用户的登陆验证,我们在对这些业务编码时,都需要考虑一下用户的登录验证。对于这种功能统一,且使用较多的功能,就可以考虑通过AOP来统一处理了。 引入AOP的思想之后,我们在处理其他业务的时候就不需要再考虑如登录验证这样的其他功能。

Spring AOP是Spring公司针对AOP思想提供的一种实现方式,除了登录验证的功能之外,Spring AOP还可以实现:统一日志记录、统一返回格式设置、统一异常处理等。

2. AOP的基本术语

2.1 切面(Aspect)

切面相当于AOP实现的某个功能的集合,比如说登录验证功能,切面是由切点(Pointcut)和通知(Advice)组成的

2.2 连接点(Join Point)

连接点是应用执行过程中能够插入切面的一个点。比如说一个博客系统,包含许多业务,其中可以插入切面的业务都可以称为连接点。

2.3 切点(Pointcut)

Pointcut的作用就是提供一组规则来匹配连接点(Join Point),比如说对于博客系统,它有一些url是不需要做登录验证功能的,比如注册业务,通过切点提供的这么一组规则,在不需要登录验证的地方,它就不会进行登录验证。

2.4 通知(Advice)

通知是切面要完成的工作,它定义了切面的具体实现是什么、何时使用,描述了切面要完成的工作以及何时执行这个宫欧的问题。Spring切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:

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

2.5 图解

以用户的登录验证为例,用图来让大家更好的理解上述定义体现的思想:

Untitled Diagram.drawio-3.png

接下来我们就来对面向切面编程的思想进行实现,如果看到这还是没有很理解AOP的思想的话,可以结合后面的实现代码再来看看这张图表达的意思。

3. Spring AOP 使用

Spring AOP的使用大体分为下面四步:

  1. 添加 Spring AOP的依赖
  2. 连接点方法的编写
  3. 定义切面和通知
  4. 定义切点

3.1 原生Maven项目中Spring AOP的使用

Spring AOP同样有两种实现方式,一种是使用xml配置的方式,一种是使用注解的方式。

在这里我将在原生的Maven项目中使用两种方式来实现搭乘地铁的业务,使用AOP的方式编写一个切面,并在切面里运用五种通知来实现搭乘地铁业务的安全检查(前置通知)、刷卡进出站(环绕通知)、通知异常(异常通知)、到达通知(返回后通知)、记录行程(后置通知)

3.1.1 xml方式使用Spring AOP

源码位置:spring-aop

1. 添加依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.9.RELEASE</version>
</dependency>

2. 连接点方法的编写:在service包下新建一个SubwayService来实现乘坐地铁的业务

public class SubwayService {
    public void takeSubway() {
        System.out.println("乘坐地铁,行驶中...");
        //int n = 10/0;
    }
}

3. 定义切面类和通知方法:在aspect包下新增一个SubwayAspect类,并在里面编写对应的通知方法

public class SubwayAspect {
    public void securityCheckAdvice() {
        System.out.println("前置通知:开始安全检查");
    }
    public void recordAdvice() {
        System.out.println("后置通知:记录本次行程");
    }

    public void expressionAdvice() {
        System.out.println("异常通知:运行过程出现异常");
    }

    public void swipeAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知start:开始并刷卡进站");
        joinPoint.proceed();
        System.out.println("环绕通知finish:结束并刷卡出站");
    }

    public void arriveDestination() {
        System.out.println("返回后通知:到达目的地");
    }
}

注意事项:swipeAdvice()是环绕通知的方法,由于其是环绕通知,因此会在连接点方法开始前和结束后的时候分别执行不同的逻辑,因此需要使用一个ProceedingJoinPoint的对象来对应连接点的方法,并使用proceed()来执行连接点方法,分别在joinPoint.proceed();语句执行的前后编写编写环绕通知。

光编写完切面和通知还没什么用,还需要在xml文件中配置才行。

配置schema路径,直接复制即可:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

将类注册到Spring容器中,这一步不多赘述:

<bean id="subwayAspect" class="com.chenshu.xml_aop.aspect.SubwayAspect"></bean>
<bean id="subwayService" class="com.chenshu.xml_aop.service.SubwayService"></bean>

配置AspectAdvice

  1. <beans>标签内添加一对<aop:config>的标签,有关aop的配置都放在这里面
  2. <aop:config>标签内添加一对<aop:aspect>标签,id属性的值自己定义,用于标识一个切面的idref属性里面的值对应前面注册入Spring的Aspect类的beanid
  3. <aop:aspect>标签内配置通知方法,不同的通知方法对应不同的标签,其中有method属性对应前面编写的通知方法的方法名,以及一个pointcut-ref来定义该通知的切点,由于我们的切点还未定义,因此这里用一个"?"替代
<aop:config>

    <aop:aspect id="subwayAspect" ref="subwayAspect">
        <aop:before method="securityCheckAdvice" pointcut-ref="?"/>
        <aop:after method="recordAdvice" pointcut-ref="?"/>
        <aop:after-throwing method="expressionAdvice" pointcut-ref="?"/>
        <aop:around method="swipeAdvice" pointcut-ref="?"/>
        <aop:after-returning method="arriveDestination" pointcut-ref="?"/>
    </aop:aspect>

</aop:config>

4. 编写切点:这里我们就要根据切点表达式来创建一个切点用于描述一组匹配规则

【引入】切点表达式

一个切点表达式就是形如上面expression属性中的内容,切点表达式中可以包括以下内容:

  • execution(表达式前缀)
  • 权限修饰符(如public、private)
  • 方法返回类型(如void、String)
  • 包名
  • 类名
  • 方法名
  • 方法的参数列表

下面是一些切点表达式的示例,可自行结合上面内容以理解:

  • execution(public * com.example.service.SomeService.*(..)):匹配 com.example.service.SomeService 类中所有 public 方法。
  • execution(public void com.example.service.SomeService.*(..)):匹配 com.example.service.SomeService 类中所有 public void 方法。
  • execution(* com.example.service.*.*(..)):匹配 com.example.service 包下所有类的所有方法。
  • execution(* com.example..*.*(..)):匹配 com.example 包下、子孙包下所有类的所有方法
  • execution(* com.example.service.SomeService.*(..)):匹配 com.example.service.SomeService 类的所有方法。
  • execution(* com.example.service.SomeService.*(String)):匹配 com.example.service.SomeService 类中接受一个 String 类型参数的所有方法。
  • execution(* *(..)):匹配任何类中的任何方法。

了解了切点表达式后我们就可以编写切点了:

<aop:pointcut id="takeSubway" expression="execution(* com.chenshu.xml_aop.service.SubwayService.takeSubway())"/>

该切点的名字为takeSubway,切点表达式的意思是匹配com.chenshu.xml_aop.service.SubwayService这个类下的名为takeSubway无传入参数的方法。

然后我们就可以在通知中填入pointcut-ref属性的值了,完整的aop配置如下:

<aop:config>
    <aop:pointcut id="takeSubway" expression="execution(* com.chenshu.xml_aop.service.SubwayService.takeSubway())"/>

    <aop:aspect id="subwayAspect" ref="subwayAspect">
        <aop:before method="securityCheckAdvice" pointcut-ref="takeSubway"/>
        <aop:after method="recordAdvice" pointcut-ref="takeSubway"/>
        <aop:after-throwing method="expressionAdvice" pointcut-ref="takeSubway"/>
        <aop:around method="swipeAdvice" pointcut-ref="takeSubway"/>
        <aop:after-returning method="arriveDestination" pointcut-ref="takeSubway"/>
    </aop:aspect>

</aop:config>

编写测试类进行测试:

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-aop.xml");
        SubwayService subwayService  =
                context.getBean("subwayService", SubwayService.class);
        subwayService.takeSubway();
    }
}
【总结】不同通知的执行顺序

在没有发生异常的情况下结果如下,通过结果可以了解不同通知的执行顺序:

前置通知:开始安全检查
环绕通知start:开始并刷卡进站
乘坐地铁,行驶中...
返回后通知:到达目的地
环绕通知finish:结束并刷卡出站
后置通知:记录本次行程

由于没有出现异常因此看不到抛异常后的通知

制造一个算数异常:

public class SubwayService {

    public void takeSubway() {
        System.out.println("乘坐地铁,行驶中.。。");
        int n = 10/0;
    }
}

发生异常的情况下结果如下,通过结果可以了解不同通知的执行顺序:

前置通知:开始安全检查
环绕通知start:开始并刷卡进站
乘坐地铁,行驶中...
异常通知:运行过程出现异常
后置通知:记录本次行程

由于方法执行一半就抛出异常,因此没有返回后的通知以及环绕通知的后半段

3.1.2 注解实现使用Spring AOP

源码位置:spring-aop_2

谈到Spring AOP的注解,就不得不谈到Spring AOP和AspectJ的关系:Spring AOP 的注解是基于 AspectJ 注解的一种简化和封装。这意味着你可以使用 AspectJ 注解来定义切面,但实际的织入过程是由 Spring AOP 来完成的。

添加依赖和xml方式是一样的,这里就不赘述了。

注解的方式只需要在xml中添加下面两个标签:上面是组件注解的扫描路径,下面是aspectj注解的声明

<context:component-scan base-package="com.chenshu.aop_annotation"/>
<aop:aspectj-autoproxy/>

编写Aspect类:

  1. Aspect类上添加@Aspect注解
  2. 定义一个方法,并在方法上使用@Pointcut注解将其声明一个切点,并在注解内添加value属性的值(切点表达式)
  3. 在通知方法上使用@Before(前置通知)、@After(后置通知)、@AfterThrowing(抛异常后通知)、@Around(环绕通知)、@AfterReturning(返回后的通知)注解声明不同类型的通知,并在注解属性中添加上一步定义的切点"myPointcut()"
@Component
@Aspect
public class SubwayAspect {
    @Pointcut(value = "execution(* com.chenshu.aop_annotation.service.SubwayService.takeSubway())")
    private void myPointcut() {}

    @Before("myPointcut()")
    public void securityCheckAdvice() {
        System.out.println("前置通知:开始安全检查");
    }

    @After("myPointcut()")
    public void recordAdvice() {
        System.out.println("后置通知:记录本次行程");
    }

    @AfterThrowing("myPointcut()")
    public void expressionAdvice() {
        System.out.println("异常通知:运行过程出现异常");
    }

    @Around("myPointcut()")
    public void swipeAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知start:开始并刷卡进站");
        joinPoint.proceed();
        System.out.println("环绕通知finish:结束并刷卡出站");
    }

    @AfterReturning("myPointcut()")
    public void arriveDestination() {
        System.out.println("返回后通知:到达目的地");
    }
}

3.2 Spring Boot项目中Spring AOP的使用

源码位置:MyBatis_demo

这里我直接基于上一篇文章中的代码来演示Spring AOP的使用。

1. 添加依赖

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

2. 新建一个aspect包编写切面类LoginAspect

@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.chenshu.mybatis_demo.controller.UserController.*(..))")
    public void myPointcut() {}

    @Before("myPointcut()")
    public void before() {
        System.out.println("进行登录验证");
    }
}

3. 测试切面是否生效:

由于我编写的切面中的前置方法对所有UserController类下的方法都生效,这里我直接访问一下UserControllergetUsers方法的路由"/getall"

image.png

查看日志信息:我们发现在执行getUsers()方法之前成功执行了前置通知

进行登录验证
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3e84be0f] was not registered for synchronization because synchronization is not active
2024-04-20 17:07:40.813  INFO 40289 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-04-20 17:07:40.890  INFO 40289 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@212054083 wrapping com.mysql.cj.jdbc.ConnectionImpl@3ef56d86] will not be managed by Spring
==>  Preparing: select * from userinfo
==> Parameters: 
<==    Columns: id, username, password, photo, createtime, updatetime, state
<==        Row: 1, zhang, 12345, doge.png, 2024-04-19 13:09:45, 2024-04-19 13:09:45, 1
<==        Row: 2, lisi, 123, , 2024-04-19 13:31:01, 2024-04-19 13:31:01, 1
<==        Row: 3, wangwu, 123, , 2024-04-19 14:17:29, 2024-04-19 14:17:29, 1
<==      Total: 3

4. 小结

本篇文章描述了AOP(面向切面编程)思想的定义,并讲解了四个基本术语连接点、切面、切点、通知的关系;然后又分别以xml配置文件和AspectJ注解的方式使用了Spring AOP。

下一篇文章将讲解有关Spring AOP的原理的内容。

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

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

相关文章

【算法分析与设计】重复的DNA

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 DNA序列 由一系列核苷酸组成&#xff0c;缩写为 A, C, G 和 T.。 例如&#xff0c;"ACGAATTCCG" 是一个 DNA序列 。 在研究…

【架构方法论(一)】架构的定义与架构要解决的问题

文章目录 一. 架构定义与架构的作用1. 系统与子系统2. 模块与组件3. 框架与架构4. 重新定义架构&#xff1a;4R 架构 二、架构设计的真正目的-别掉入架构设计的误区1. 是为了解决软件复杂度2. 简单的复杂度分析案例 三. 案例思考 本文关键字 架构定义 架构与系统的关系从业务逻…

前端零代码开发实践:页面嵌套+逻辑连线0开发扩展组件,实现切换开关控制扇叶转动。能无代码封装扩展组件,有别于常规的web组态或低代码平台

前言&#xff1a; 官网:http://www.uiotos.net/ 什么是 UIOTOS&#xff1f; 这是一款拥有独创专利技术的前端零代码工具&#xff0c;专注于解决前端界面开发定制难题&#xff0c;原型即应用&#xff01;具有页面嵌套、属性继承、节点连线等全新特性&#xff0c;学习门槛低…

OpenCV 如何实现边缘检测器

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV如何实现拉普拉斯算子的离散模拟 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数…

基于RK3588的全国产鸿蒙边缘计算工控机在智能交通ETC收费系统的应用

1.1 产品简介 基于智能交通、工业互联等行业快速智能化发展的需求&#xff0c;以 OpenHarmony 为框架开发嵌入 HamonyOS&#xff0c;打造了具有高智能、高可靠、高安全的自主 可控的边缘处理器 XM-RK3588。 图 1-1 边缘处理器 HamonyOS强化 IoT 互联互动能力&#xff0c;让边缘…

【JAVA基础之IO】字节流、字符流以及乱码问题

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;mysql专栏&#xff1a;小林同学的专栏 目录 1. IO概述 1.1 什么是IO 1.2 IO的分类 1.3 字节和字符流的顶级父类 2. 字节流 2.1 一切皆为字节 2.2 字节输出流【OutputStream】 2.3 FileOutputStream类…

【Python】自定义修改pip下载模块默认的安装路径

因为电脑下载了Anaconda提供的默认Python 3.9 以及后期下载的python3.10所以在Pychram进行项目开发时&#xff0c;发现一些库怎么导入都导入不了&#xff0c;手动install也是失败&#xff0c;后期在cmd里面发现python以及pip配置有点儿混乱&#xff0c;导致执行命令时&#xff…

学习c语音的自我感受

因为是自学&#xff0c;所以走过不少弯路。去年&#xff0c;受知乎“python性能弱”风潮的影响&#xff0c;学过go,rust。 在学习这些新语言的时候&#xff0c;由衷感受到&#xff0c;或是本身侧重方向的原因&#xff08;如go侧重服务器&#xff09;&#xff0c;或是语言太新不…

01-服务与服务间的通信

这里是极简版&#xff0c;仅用作记录 概述 前端和后端可以使用axios等进行http请求 服务和服务之间也是可以进行http请求的spring封装的RestTemplate可以进行请求 用法 使用bean注解进行依赖注入 在需要的地方&#xff0c;自动注入RestTemplate进行服务和服务之间的通信 注…

探索React Router:实现动态二级路由

我有一个路由配置的二维数组&#xff0c;想根据这个数组结合路由组件来动态生成路由&#xff0c;应该怎么样实现。在 React Router 6 中渲染二级路由的方式跟 React Router 65相比有一些变化,但核心思路仍然是利用 Route 组件和路由嵌套的方式。下面是具体的步骤: 定义路由数组…

C系统编程:从零手搓一个shell

背景 这么久没更新就是在干这件事&#xff01;&#xff01;因为系统编程已经学的差不多了&#xff0c;所以想找几个项目练练手&#xff0c;之前就一直想写一个自己的shell&#xff01;&#xff01;现在终于有机会实现了。 首先说明一下我的操作系统&#xff1a;Arch linux 服务…

C++ - STL详解(七)— stack和queue的介绍及使用

目录 一. stack 1.1 stack的介绍 1.2 stack的定义 1.3 stack的使用 ​编辑 二. queue 2.1 queue的介绍 2.2 queue的定义 2.3 queue的使用 一. stack 1.1 stack的介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除…

redis底层数据结构之ziplist

目录 一、概述二、ziplist结构三、Entry结构四、为什么ZipList特别省内存五、ziplist的缺点 redis底层数据结构已完结&#x1f44f;&#x1f44f;&#x1f44f;&#xff1a; ☑️redis底层数据结构之SDS☑️redis底层数据结构之ziplist☑️redis底层数据结构之quicklist☑️red…

ETL工具-nifi干货系列 第十六讲 nifi Process Group实战教程,一文轻松搞定

1、目前nifi系列已经更新了10多篇教程了&#xff0c;跟着教程走的同学应该已经对nifi有了初步的解&#xff0c;但是我相信同学们应该有一个疑问&#xff1a;nifi设计好的数据流列表在哪里&#xff1f;如何同时运行多个数据流&#xff1f;如启停单个数据流&#xff1f; 带着这些…

第二期书生浦语大模型训练营第四次笔记

大模型微调技术 大模型微调是一种通过在预训练模型的基础上&#xff0c;有针对性地微调部分参数以适应特定任务需求的方法。 微调预训练模型的方法 微调所有层&#xff1a;将预训练模型的所有层都参与微调&#xff0c;以适应新的任务。 微调顶层&#xff1a;只微调预训练模型…

Pandas数据分析小技巧

Pandas数据分析小技巧&#xff1a;提升数据处理效率与准确性的秘诀 Pandas是一个强大的Python数据分析库&#xff0c;它提供了快速、灵活且富有表现力的数据结构&#xff0c;使得数据清洗、转换、分析等操作变得简单而高效。本文将介绍一些Pandas数据分析的小技巧&#xff0c;…

年如何在不丢失数据的情况下解锁锁定的 Android 手机?

当您忘记密码、PIN 码或图案并且想要解锁 Android 手机时&#xff0c;您可能会丢失 Android 手机上的数据。但您无需再担心&#xff0c;因为在这里&#xff0c;我们想出了几种解锁锁定的 Android 手机而不丢失数据的方法。 方法 1. 使用 Android Unlock 解锁锁定的 Android 且不…

【上海大学计算机组成原理实验报告】四、指令系统实验

一、实验目的 了解指令结构、PC寄存器的功能和指令系统的基本工作原理。 学习设计指令的方法。 二、实验原理 根据实验指导书的相关内容&#xff0c;对于部分使用频率很高&#xff0c;且只用几条微指令即可完成的简单操作&#xff0c;可以把这部分简单操作的微指令序列固定下…

mfc140.dll丢失如何修复,分享多种有效的修复方法

在日常操作和使用电脑的过程中&#xff0c;我们可能会遇到一种较为常见的问题&#xff0c;即在尝试启动或运行某个应用程序时&#xff0c;系统突然弹出一个错误提示窗口&#xff0c;明确指出“mfc140.dll文件丢失”。这个mfc140.dll实际上是一个动态链接库文件&#xff08;DLL&…

linux运行ant 报错 Unable to locate tools.jar【已解决】

linux安装 ant 运行时报错 Unable to locate tools.jar. Expected to find it in /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.402.b06-1.el7_9.x86_64/lib/tools.jar 原因 已安装的jdk只有运行环境&#xff0c;没有tool.jar&#xff0c;而ant运行需要java开发环境&#xff0c;因…