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

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的原理的内容。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

js手写call、bind、apply

目录 call与applyapply bind call和apply和bind有两种实现方式&#xff0c;第一种是隐式绑定&#xff0c;第二种是通过new 无论是通过隐式绑定实现还是通过new实现&#xff0c;核心都是针对this的绑定规则 具体关于this的绑定规则可以看我这一篇博客 this绑定规则 call与apply…

【热议】硕士和读博士洗碗区别的两大理论

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

【软件测试基础】概述篇(持续更新中)

《 软件测试基础持续更新中》 这一章&#xff0c;是每一名软件测试工程师必须要掌握的常识&#xff01; 1、软件测试的目的&#xff1a;提高软件质量 和 确保软件满足用户需求。 2、软件测试的概念&#xff1a;使用人工或自动手段来运行或测试某个系统的过程&#xff0c;目的…

品牌差异化战略:Kompas.ai如何打造独特的内容声音

在当今竞争激烈的商业环境中&#xff0c;品牌差异化已成为企业获取市场优势的关键策略。一个鲜明的品牌形象和独特的内容声音不仅能够帮助企业吸引目标客户&#xff0c;还能够在消费者心中建立起独特的地位。本文将深入探讨品牌差异化的重要性&#xff0c;分析Kompas.ai如何帮助…

SL3037内置MOS管 耐压60V降压恒压芯片 降12V或降24V 电路简单

SL3037B是一款内置功率MOSFET的单片降压型开关模式转换器&#xff0c;具有以下特点&#xff1a; 1. 高效率&#xff1a;采用开关式降压技术&#xff0c;仅在需要调节输出电压时才会消耗能量&#xff0c;从而提高了整体的效率。 2. 稳定性好&#xff1a;通过精确的内部电路设计…

数睿通2.0版本升级:探索数据血缘的奥秘

引言 数睿通 2.0 迎来了 4 月份的更新&#xff0c;该版本更新了许多用户期望的数据血缘模块&#xff0c;把原来外链跳转 neo4j 页面改为自研页面&#xff0c;方便后期的二次开发完善&#xff0c;此外&#xff0c;新版本摒弃了 neo4j 的血缘数据存储方案&#xff0c;一来是因为…

接口压力测试 jmeter--进阶篇(三)

一、数据实时监控JMeterGrafanaInfluxdb &#xff08;mac&#xff09;性能监控平台搭建JMeterGrafanaInfluxdb 优点&#xff1a; 1.实时 2.美观 3.能够存储和对比 原理&#xff1a; 1.运行jmeter时会吧数据写入到influxdb 2.influxdb实时存储执行的结果 3.grafana链接.influxd…

基于 Flexbox 的纯 CSS 框架:兼容性好、文档丰富 | 开源日报 No.232

jgthms/bulma Stars: 48.3k License: MIT bulma 是基于 Flexbox 的现代 CSS 框架。 基于 Flexbox 技术。提供快速安装方式&#xff0c;支持 NPM、Yarn 和 Bower。仅包含 CSS 文件&#xff0c;没有 JavaScript 部分。兼容性良好&#xff0c;在主流浏览器上运行良好。提供丰富的…

工作中常用的5种加密算法

背景 最近&#xff0c;项目中做了一些安全性要求的整改。而加密是使用过程中常用的手段之一。这里简单的整理下&#xff0c;希望对小伙伴有帮助。 使用场景 加密是一种将原始信息&#xff08;明文&#xff09;转换成难以被直接理解的形式&#xff08;密文&#xff09;的过程…

信息收集

信息收集 域名的相关知识 域名的技术指的是一个域名由多少级组成&#xff0c;域名的各个级别被“.”分开&#xff0c;简而言之&#xff0c;有多少个点就是几级域名 顶级域名&#xff1a;.com(商)、.edu(教)、.gov(政)、.mil(军) 一级域名&#xff1a;qq.com 二级域名&#xf…

Python--容器、面向对象

一、容器类型(下) 重点学习容器的定义 常用操作的建议 跟着课堂把代码写一遍即可&#xff0c;混个脸熟&#xff0c;后面现用现查 增、删、改、查&#xff1a;重点掌握 查 字符串、元组&#xff1a;只能查&#xff0c;不能改 1.1 字符串str 1.1.1 字符串基本语法 1.1.2 字…

外呼系统出海注意事项

外呼系统在出海过程中需要注意多个方面&#xff0c;以确保系统的有效运行和合规性。以下是一些关键的注意事项&#xff1a; 一、市场调研与目标定位&#xff1a; 在出海前&#xff0c;深入调研目标市场的行业趋势、消费者偏好、文化背景和竞争态势。这有助于企业更好地了解市场…

基于麻雀搜索算法-BP神经网络SSA-BP回归预测

文章目录 效果一览文章概述订阅专栏只能获取一份代码部分源码参考资料效果一览 文章概述 基于麻雀搜索算法-BP神经网络SSA-BP回归预测 订阅专栏只能获取一份代码 部分源码 %------

几种Python处理Excel数据的方法!

第一种方法&#xff1a; 电子表格格式 我们在日常工作中常常见到各种后缀的电子表格&#xff0c;例如最常见的xlsx以及较为常见的csv、xls等格式的表格。同样是电子表格&#xff0c;它们之间有什么区别吗&#xff1f; • xls为Excel早期表格格式。 xls格式是Excel2003版本及…

TDengineGUI无法连接TDengine

可能是TDengineGUI本身的问题&#xff0c;直接下载可执行文件即可 下载地址&#xff1a;Release 1.0.3 ericyangpan/TDengineGUI (github.com) 还有可能是你的6041端口没开注意检查 可在TDengine文件夹下直接执行 systemctl start taosadapter 也可以点击 taosadapter.exe …

管理者如何在团队里讨论敏感话题

在团队中处理不便讨论的敏感话题可能会令人不适&#xff0c;但如果无视问题&#xff0c;它们会不知不觉地积聚起来&#xff0c;影响士气。 随着团队地理上越来越分散以及线上沟通增多&#xff0c;感知到团队间的不适或提出敏感话题变得更加困难。但是&#xff0c;忽略这些难以…

企业组网的作用有哪些?SD-WAN有什么优势?

在信息化的时代&#xff0c;企业组网建设逐渐成为提升企业竞争力、实现业务高效运行的关键&#xff0c;它能够为企业在信息传输、资源共享、远程办公等方面提供强大的支持。接下来&#xff0c;本文为大家详细介绍企业网络组网的意义&#xff0c;以及为大家推荐热门的网络解决方…

JVM (Micrometer)监控SpringBoot(AWS EKS版)

问题 怎样使用JVM (Micrometer)面板&#xff0c;监控Spring&#xff1f;这里不涉及Prometheus和Grafana&#xff0c;重点介绍与Micrometer与Springboot&#xff0c;k8s怎样集成。 pom.xml 引入依赖&#xff0c;如下&#xff1a; <properties><micrometer.version&…

【缓存服务】⭐️自定义实现一个简易的数据缓存

目录 &#x1f378;前言 &#x1f37b;手写缓存服务 &#xff08;1&#xff09;缓存实体类 &#xff08;2&#xff09;缓存工具类 &#xff08;3&#xff09;测试缓存服务 &#x1f377;已有的缓存工具 &#x1f379;章末 &#x1f378;前言 俗话说 有轮子不用 就是玩 开个…

推荐3个视频转文字的工具

如果是长视频转文字的话&#xff0c;我会推荐你三个专业的能够将视频文字提取出来的工具&#xff0c;操作无门槛&#xff0c;转换出的文字准确率高&#xff0c;可以直接导出文字。 1.一键识别王 https://www.xunjiepdf.com/yijianshibiewang 专业的图片文字识别软件&#xff0…