Spring AOP:横切关注点的优雅解决方案

目录

概要

和面向对象编程的区别

优点

AOP的底层原理

JDK动态代理技术

AOP七大术语

切点表达式

AOP实现方式

Spring对AOP的实现包括以下3种方式:

在本篇文章中,我们主要讲解前两种方式。

基于AspectJ的AOP注解式开发

定义目标类以及目标方法

定义切面类

目标类和切面类都纳入Spring bean管理

在切面类中添加通知

再通知上添加切点表达式

在Spring配置文件中启用自动代理

通知类型

接下来,编写程序来测试这几个通知的执行顺序

切面的先后顺序

优先使用切点表达式

基于XML配置方式的AOP

编写目标类

编写切面类,并编写通知

编写Spring配置文件


概要

在软件开发的旅程中,我们常常会遇到一些横切关注点(cross - cutting concerns),这些关注点如同贯穿整个应用程序的丝线,涉及多个不同的模块和功能。例如,日志记录、事务管理、权限验证等功能,它们并非属于某个特定的业务模块,却又在许多业务方法中都需要被执行。​

在传统的编程模式下,为了实现这些横切关注点,开发者往往不得不将相关的代码片段分散地嵌入到各个业务方法中。以日志记录为例,可能在每个需要记录日志的业务方法开头和结尾都要添加打印日志的代码,这不仅导致代码的大量重复,还使得业务逻辑与这些辅助功能的代码紧密纠缠在一起,严重影响了代码的可读性、可维护性和可扩展性。​

Spring AOP 应运而生,它提供了一种优雅而强大的解决方案,能够将这些横切关注点从业务逻辑中分离出来,以一种模块化的方式进行集中管理和维护。通过定义切面(Aspect),Spring AOP 可以在不修改业务逻辑代码的前提下,在特定的连接点(Join Point,如方法调用、异常抛出等)插入相应的通知(Advice,如前置通知、后置通知、环绕通知等),从而实现对横切关注点的统一处理。


和面向对象编程的区别

维度面向对象编程面向切面编程
核心思想以 “对象” 为中心,将现实世界抽象为类、对象及对象间的交互。以 “切面” 为中心,将横切关注点从业务逻辑中分离,实现模块化管理。
基本单元类(Class)和对象(Object),通过封装、继承、多态构建程序结构。切面(Aspect),通过定义切点(Pointcut)和通知(Advice)织入横切逻辑。
关注点类型聚焦于业务逻辑的纵向模块化(如用户管理、订单处理等独立业务模块)。聚焦于横切关注点的横向抽取(如日志、权限、事务等贯穿多个模块的功能)。

优点

  1. 代码复用性强
  2. 代码易维护
  3. 使开发者更专注于业务逻辑

AOP的底层原理

JDK动态代理技术

为接口创建代理类的字节码文件,使用ClassLoader将字节码文件加载到JVM,创建代理类实例对象,执行对象的目标方法。

AOP七大术语

  1. 连接点Joinpoint:指那些被拦截到的点(位置)。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  2. 切点Pointcut:指我们要对哪些Joinpoint进行拦截的定义,在程序执行流程中,真正织入切面的方法(一个切点对应多个连接点)
  3. 切面Aspect:切点+通知就是一个切面,需要自己编写和配置
  4. 通知Advice:通知又叫增强,就是具体你要织入的代码
  5. 织入Weaving:把通知应用到目标对象上的过程(指把增强应用到目标对象来创建新的代理对象的过程)
  6. 代理对象 Proxy:一个目标对象被织入通知后产生的新对象
  7. 目标对象 Target:被织入通知的对象

其中的通知(Aspect)包括:

  • 前置通知:befer
  • 后置通知:after-returning
  • 最终通知:after
  • 异常通知:throwing
  • 环绕通知:around
public class Cat {​// Cat类的run方法​public void run() {​System.out.println("Cat is running");​}​
}​
​
class Test {​public static void main(String[] args) {​Cat cat = new Cat();​cat.run();​}​
}

切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入,语法格式如下: 

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
  • 访问权限控制符:(可选项)没写就是4种权限都可以
  • 返回值类型:(必填项)若为“*”,表示返回值类型任意 
  • 全限定类名:(可选项)两个点“..”表示当前包以及子包下的所有类,若省略,就表示所有的类
  • 方法名:(必填项)“*”表示所有方法,set * 表示所有的set方法
  • 形式参数列表:(必填项)
    • ():表示没有参数的列表
    • (..) :参数类型和个数随意的方法
    • (*): 只有一个参数的方法
    • (*, String): 第一个参数类型随意,第二个参数是String的
  • 异常:(可选项)省略是表示任意异常

如:

execution(public * com.powernode.mall.service.*.delete*(..))
表示返回值类型任意,处于com.powernode.mall.service包下的所有类的所有参数任意的deleteXxx方法execution(* com.powernode.mall..*(..))
任意修饰符、返回值类型的,处于com.powernode.mall报下的所有方法execution(* *(..))
表示该项目的所有方法

AOP实现方式

Spring对AOP的实现包括以下3种方式:

  1. Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  2. Spring框架结合AspectJ框架实现的AOP,基于XML方式。
  3. Spring框架自己实现的AOP,基于XML配置方式。

在本篇文章中,我们主要讲解前两种方式。

基于AspectJ的AOP注解式开发

定义目标类以及目标方法
// 目标类
public class OrderService {// 目标方法public void generate(){System.out.println("订单已生成!");}
}
定义切面类
// 切面类
@Aspect
public class MyAspect {
}

注解@Aspect会告诉Spring该类是一个注解类

目标类和切面类都纳入Spring bean管理

在目标类OrderService上添加@Component注解

在切面类MyAspect类上添加**@Component**注解

在切面类中添加通知

// 切面类
@Aspect
@Component
public class MyAspect {// 这就是需要增强的代码(通知)public void advice(){System.out.println("我是一个通知");}
}

再通知上添加切点表达式

// 切面类
@Aspect
@Component
public class MyAspect {// 切点表达式@Before("execution(* com.xxx.spring6.service.OrderService.*(..))")// 这就是需要增强的代码(通知)public void advice(){System.out.println("我是一个通知");}
}

其中,注解@Before表示前置通知(具体的通知类型在上面有讲过,下面也会有方法中的通知注解)

在Spring配置文件中启用自动代理

 <!--开启组件扫描--><context:component-scan base-package="com.xxx.spring6.service"/><!--开启自动代理--><aop:aspectj-autoproxy proxy-target-class="true"/>

<aop:aspectj-autoproxy  proxy-target-class="true"/> 开启自动代理之后,凡是带有@Aspect注解的bean都会生成代理对象。

proxy-target-class="true" 表示采用cglib动态代理。

proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类(AOP的底层就是动态代理,关于动态代理的内容可查看代理机制)   

通知类型

  • 前置通知:@Before 目标方法执行之前的通知
  • 后置通知:@AfterReturning 目标方法执行之后的通知
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知
  • 最终通知:@After 放在finally语句块中的通知

接下来,编写程序来测试这几个通知的执行顺序

// 切面类
@Component
@Aspect
public class MyAspect {@Around("execution(* com.xxx.spring6.service.OrderService.*(..))")public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕通知开始");// 执行目标方法。proceedingJoinPoint.proceed();System.out.println("环绕通知结束");}@Before("execution(* com.xxx.spring6.service.OrderService.*(..))")public void beforeAdvice(){System.out.println("前置通知");}@AfterReturning("execution(* com.xxx.spring6.service.OrderService.*(..))")public void afterReturningAdvice(){System.out.println("后置通知");}@AfterThrowing("execution(* com.xxx.spring6.service.OrderService.*(..))")public void afterThrowingAdvice(){System.out.println("异常通知");}@After("execution(* com.xxx.spring6.service.OrderService.*(..))")public void afterAdvice(){System.out.println("最终通知");}}

读者可自行测试,其中异常通知需要产生异常才能触发,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,“后置通知”和“环绕通知”的结束部分不会执行

切面的先后顺序

我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高

优先使用切点表达式

  • 上面的切点表达式重复写了多次,没有得到复用,同时如果要修改切点表达式,需要修改多处,难维护
  • 我们可以将切点表达式单独的定义出来,在需要的位置引入即可
// 切面类
@Component
@Aspect
@Order(2)
public class MyAspect {@Pointcut("execution(* com.xxx.spring6.service.OrderService.*(..))")public void pointcut(){}@Around("pointcut()")public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕通知开始");// 执行目标方法。proceedingJoinPoint.proceed();System.out.println("环绕通知结束");}@Before("pointcut()")public void beforeAdvice(){System.out.println("前置通知");}@AfterReturning("pointcut()")public void afterReturningAdvice(){System.out.println("后置通知");}@AfterThrowing("pointcut()")public void afterThrowingAdvice(){System.out.println("异常通知");}@After("pointcut()")public void afterAdvice(){System.out.println("最终通知");}}

使用@Pointcut注解来定义独立的切点表达式。

注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置

基于XML配置方式的AOP

编写目标类

// 目标类
public class VipService {public void add(){System.out.println("保存vip信息。");}
}

编写切面类,并编写通知

// 负责计时的切面类
public class TimerAspect {public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long begin = System.currentTimeMillis();//执行目标proceedingJoinPoint.proceed();long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");}
}

编写Spring配置文件

    <!--纳入spring bean管理--><bean id="vipService" class="com.xxx.spring6.service.VipService"/><bean id="timerAspect" class="com.xxx.spring6.service.TimerAspect"/><!--aop配置--><aop:config><!--切点表达式--><aop:pointcut id="p" expression="execution(* com.xxx.spring6.service.VipService.*(..))"/><!--切面--><aop:aspect ref="timerAspect"><!--切面=通知 + 切点--><aop:around method="time" pointcut-ref="p"/></aop:aspect></aop:config>
</beans>

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

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

相关文章

开源 Arkts 鸿蒙应用 开发(三)Arkts语言的介绍

文章的目的为了记录使用Arkts 进行Harmony app 开发学习的经历。本职为嵌入式软件开发&#xff0c;公司安排开发app&#xff0c;临时学习&#xff0c;完成app的开发。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; 开源 Arkts …

hot100 -- 16.多维动态规划

1.不同路径 问题&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条…

优先级继承和优先级天花板(pthread_mutexattr_setprotocol)

优先级继承和优先级天花板&#xff0c;均可以解决优先级翻转问题。 优先级翻转&#xff1a; 实例观察优先级翻转和优先级继承现象-CSDN博客 如果有两个线程A和B&#xff0c;A的优先级大于B的优先级。在B获取锁之后&#xff0c;释放锁之前&#xff0c;A想要获取锁&#xff0c…

Spark on Hive表结构变更

Spark on Hive表结构变更 1、表结构变更概述1、表结构变更概述 在Spark on Hive架构中,表结构(Schema)变更是一个常见且重要的操作。理解其背景、使用场景以及具体方式对于大数据平台管理至关重要 1.1、Spark on Hive元数据管理 Hive Metastore(HMS): 核心组件。它是一个…

NotePad++ 怎么没有找到插件管理?

今天想使用NotePad阅读markdown文档&#xff0c;却发现未安装插件&#xff0c;本想通过插件管理安装一下&#xff0c;结果没有插件管理&#xff01;&#xff01;&#xff01;&#xff01; 我发现帮助菜单里面有一个入口 可惜的是网页无法打开。。。 只能自己下载插件了。 将插件…

内容搜索软件AnyTXT.Searcher忘记文件名也能搜,全文检索 1 秒定位文件

各位文件搜索小能手们&#xff01;你们有没有过这种糟心事儿&#xff0c;想找个文件&#xff0c;死活想不起文件名&#xff0c;在电脑里一顿乱翻&#xff0c;眼睛都找瞎了也没找到。今天我就给你们介绍一款神器——AnyTXT.Searcher&#xff0c;它可是免费的全文检索工具&#x…

uniapp实现像qq消息列表左滑显示右侧操作栏效果

先看效果图 代码 SlidableChatEntry.vue <template><!-- 聊天项列表 --><view class"chat-item"touchstart"handleTouchStart($event)"touchmove"handleTouchMove($event)"touchend"handleTouchEnd()"><!-- 聊…

收集了一些用python做mysql增删改查的资料

还是因为最近在开发fastapi应用、现在需要把一些关键信息存库&#xff0c;所以就很想要一些这方面的资料。我这里找到一些&#xff0c;希望你看了帖子能节省一些时间。 前边说过如何搭建fastapi开发环境&#xff0c;帖子链接为&#xff1a; https://blog.csdn.net/weixin_4298…

嵌入式软件面经(一)Q: 什么是Modbus协议?它有哪些特点?

Modbus协议是一种开放式的工业通讯协议&#xff0c;最初由Modicon公司&#xff08;现施耐德电气Schneider Electric&#xff09;于1979年开发&#xff0c;广泛应用于工业现场设备之间的数据通讯&#xff0c;尤其适用于PLC&#xff08;可编程逻辑控制器&#xff09;与现场仪表、…

Java-52 深入浅出 Tomcat SSL工作原理 性能优化 参数配置 JVM优化

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月13日更新到&#xff1a; AI炼丹日志-29 - 字节…

day44-硬件学习之arm启动代码

一、跳转指令实现函数调用 LR&#xff08;链接寄存器&#xff09;&#xff1a;保留函数返回地址 1.1 bl指令 跳转指令bl,使LR保存当前函数进入前的下一条指令的地址&#xff0c;使函数调用后可以返回下一条指令的地址&#xff1b; 1.2 bx指令 跳到目标地址&#xff1b; 1.3 栈…

【数据结构】七种常见排序算法

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 欢迎来到排序算法的学习&#xff0c;恭喜你&#xff01;本期内容主要介绍排序算法&#xff0c;一起来探索吧~ &#xf…

Spring AOP 代理模式解析

一、核心概念&#xff1a;代理模式就像房屋中介 想象你要租一套房子&#xff1a; 你&#xff1a;租客&#xff08;业务调用者&#xff09;房东&#xff1a;房主&#xff08;真实业务对象&#xff09;中介&#xff1a;代理对象 传统方式&#xff08;无代理&#xff09; 租客…

智能制造——案例解读16页制造业指标体系搭建指导【附全文阅读】

文档的主要内容可以总结如下&#xff1a; **文档概述**&#xff1a; 本文档详细探讨了企业为何需要指标体系、指标体系的定义、如何搭建指标体系、如何有效拆解和管理指标&#xff0c;并最后提供了制造业指标体系的参考。 **主要内容**&#xff1a; 1. **企业为什么需要指标体系…

Pandas 数据清洗

数据清洗是数据分析过程中至关重要的一环&#xff0c;也是初学者最容易忽视的步骤。本文将详细介绍如何使用Pandas进行数据清洗&#xff0c;涵盖空值处理、日期格式修正、错误数据识别和重复数据删除四大核心内容。 1. Pandas 清洗空值 空值是数据集中最常见的问题之一&#…

C++容器之 forward_list (单向链表)使用说明

目录 1. 语法格式 2. 说明 3. 用法示例 1. 语法格式 描述控制可变长度元素序列的对象。该序列存储为单向(前向)链接的节点列表&#xff0c;每个节点包含一个 Type 类型的成员。 template <class Type, class Allocator allocator<Type>> class forward_lis…

ali 轻量服务器安装nginx

# Ubuntu sudo apt install nginx-light # 精简版 # CentOS sudo yum install nginx #启动并设置开机自启 sudo systemctl daemon-reload sudo systemctl start nginx sudo systemctl enable nginx #验证安装 nginx -v curl -I 127.0.0.1 #常用命令&#xff1a; # 重新加载配…

【设计模式】4.代理模式

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 代理模式 1. 第一版 class SchoolGirl:def __init__(self):self._name Nonepropertydef name(self):return self._namename.setterdef name(self, valu…

基于微信小程序的美食点餐订餐系统

文章目录 1. 项目概述2. 项目思维导图3. 系统架构特点4. 核心模块实现代码1. 登录注册2. 首页模块实现4. 分类模块实现5. 购物车模块实现6. 订单模块实现 5. 注意事项6. 项目效果截图7. 关于作者其它项目视频教程介绍 1. 项目概述 在移动互联网时代&#xff0c;餐饮行业数字化…

[neo4j]介绍4个开源的知识图谱项目

项目主要介绍几个开源项目&#xff1a; QASystemOnMedicalKG&#xff1a;医疗知识图谱问答 https://github.com/liuhuanyong/QASystemOnMedicalKG Agriculture_KnowledgeGraph&#xff1a;农业知识图谱 Financial-Knowledge-Graphs&#xff1a;小型金融知识图谱 stock-know…