【AOP实战】掌握Spring Boot AOP:重构代码,提升效率

文章目录

  • Spring Boot AOP - 面向切面编程
    • AOP到底有什么不同
    • AOP中的编程术语和常用注解
    • 定义切面
    • 环绕通知
    • 通知方法传参
    • 总结

Spring Boot AOP - 面向切面编程

AOP,即面向切面编程,其核心思想就是把业务分为核心业务非核心业务两大部分。例如一个论坛系统,用户登录、发帖等等这是核心功能,而日志统计等等这些就是非核心功能。
在Spring Boot AOP中,非核心业务功能被定义为切面,核心和非核心功能都开发完成之后,再将两者编织在一起,这就是AOP。
AOP的目的就是将那些与业务无关,却需要被业务所用的逻辑单独封装,以减少重复代码,减低模块之间耦合度,利于未来系统拓展和维护。
今天,我将做一个简单的打印用户信息的程序,即后端接受POST请求中的User对象将其打印这样一个逻辑,在这个上面实现AOP。
首先放上用户打印服务逻辑的方法代码:
User类:

public class User implements Serializable {private int id;private String username;private String nickname;
}

Service层:

package com.example.springbootaop.service.impl;import com.example.springbootaop.model.User;
import com.example.springbootaop.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Component
public class UserServiceImpl implements UserService {/*** 使用Logger*/private Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic void printUserInfo(User user) {logger.info("用户id:" + user.getId());logger.info("用户名:" + user.getUsername());logger.info("用户昵称:" + user.getNickname());}}

Controller层:

package com.example.springbootaop.api;import com.example.springbootaop.model.User;
import com.example.springbootaop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserAPI {@Autowiredprivate UserService userService;@PostMapping("/user")public String printUser(@RequestBody User user) {userService.printUserInfo(user);return "已完成打印!";}}

AOP到底有什么不同

这里只是实现一个简单的逻辑,打印用户信息,这就是我们今天的核心功能。

如果这个时候我们要给它加上非核心功能:在打印之前和打印之后分别执行一个方法,如果你不知道AOP,你可能会把Controller层的方法改成如下形式:

@PostMapping("/user")
public String printUser(@RequestBody User user) {// 执行核心业务之前doBefore();// 执行核心业务userService.printUserInfo(user);// 执行核心业务之后doAfter();...return "已完成打印!";
}

如果说方法多了,业务多了,非核心业务的逻辑一变,所有Controller的全部方法都要改动,非常麻烦,且代码冗余,耦合度高。
这时,就需要AOP来解决这个问题。
AOP只需要我们单独定义一个切面,在里面写好非核心业务的逻辑,即可将其织入核心功能中去,无需我们再改动Service层或者Controller层。

AOP中的编程术语和常用注解

在学习AOP之前,我们还是需要了解一下常用术语:

  • 切面:非核心业务功能就被定义为切面。比如一个系统的日志功能,它贯穿整个核心业务的逻辑,因此叫做切面
  • 切入点:在哪些类的哪些方法上面切入
  • 通知:在方法执行前/后或者执行前后做什么
    • 前置通知:在被代理方法之前执行
    • 后置通知:在被代理方法之后执行
    • 返回通知:被代理方法正常返回之后执行
    • 异常通知:被代理方法抛出异常时执行
    • 环绕通知:是AOP中强大、灵活的通知,集成前置和后置通知
  • 切面:在什么时机、什么地方做什么(切入点+通知)
  • 织入:把切面加入对象,是生成代理对象并将切面放入到流程中的过程(简而言之,就是把切面逻辑加入到核心业务逻辑的过程)

在Spring Boot中,我们使用@AspectJ注解开发AOP,首先需要在pom.xml中引入如下依赖:

<!-- Spring Boot AOP -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后就可以进行AOP开发了!
这里先给出常用注解,大家联系着下面的例子看就可以了:

  • @Pointcut 定义切点
  • @Before 前置通知
  • @After 后置通知
  • @AfterReturning 返回通知
  • @AfterThrowing 异常通知
  • @Around 环绕通知

定义切面

新建aop包,在里面新建类作为我们的切面类,先放出切面类代码:

package com.example.springbootaop.aop;import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LogAspect {/*** 日志打印*/private Logger logger = LoggerFactory.getLogger(this.getClass());/*** 使用Pointcut给这个方法定义切点,即UserService中全部方法均为切点。<br>* 这里在这个log方法上面定义切点,然后就只需在下面的Before、After等等注解中填写这个切点方法"log()"即可设置好各个通知的切入位置。* 其中:* <ul>*     <li>execution:代表方法被执行时触发</li>*     <li>*:代表任意返回值的方法</li>*     <li>com.example.springbootaop.service.impl.UserServiceImpl:这个类的全限定名</li>*     <li>(..):表示任意的参数</li>* </ul>*/@Pointcut("execution(* com.example.springbootaop.service.impl.UserServiceImpl.*(..))")public void log() {}/*** 前置通知:在被代理方法之前调用*/@Before("log()")public void doBefore() {logger.warn("调用方法之前:");logger.warn("接收到请求!");}/*** 后置通知:在被代理方法之后调用*/@After("log()")public void doAfter() {logger.warn("调用方法之后:");logger.warn("打印请求内容完成!");}/*** 返回通知:被代理方法正常返回之后调用*/@AfterReturning("log()")public void doReturning() {logger.warn("方法正常返回之后:");logger.warn("完成返回内容!");}/*** 异常通知:被代理方法抛出异常时调用*/@AfterThrowing("log()")public void doThrowing() {logger.error("方法抛出异常!");}}

切面类需要打上@Aspect注解表示这是一个切面类,然后不要忘了打上@Component注解。
我们逐步来看。
首先是定义切点,只需定义一个空方法,在上面使用@Pointcut注解即可,注解里面内容含义如下:

  • execution 代表方法被执行时触发
  • * 代表任意返回值的方法
  • com.example.springbootaop.service.impl.UserServiceImpl 被织入类的全限定名
  • (..) 表示任意的参数

定义完切点之后,就可以定义各个通知的方法逻辑了,这些就是我们的切面逻辑,也就是非核心业务的逻辑。
上面在doBefore方法上面,我们使用了@Before注解,这样就标明了doBefore方法是前置通知逻辑,会在被织入方法之前执行。我们把log方法定义为切入点,然后下面各个通知注解中,填写这个切入点方法名称即可。
我们也并不需要定义所有的通知,只需定义需要的即可。
其实,如果不定义上面的切入点方法log@Pointcut,你仍然可以把execution表达式直接写在各个通知的注解里面,例如:

/*** 前置通知:在被代理方法之前调用*/
@Before("execution(* com.example.springbootaop.service.impl.UserServiceImpl.*(..))")
public void doBefore(JoinPoint joinPoint) {logger.warn("调用方法之前:"); logger.warn("接收到请求!");
}

但是大多数情况并不推荐这样,这种写法较为复杂。

我们发送一个请求测试一下:
在这里插入图片描述
通过这个,我们也可以发现各个通知的执行顺序:

Before -> AfterReturning -> After

环绕通知

环绕通知是AOP中最强大的通知,可以同时实现前置和后置通知,不过它的可控性没那么强,如果不用大量改变业务逻辑,一般不需要用到它。我们在上述切面加入下列环绕通知方法:

/*** 环绕通知*/
@Around("log()")
public void around(ProceedingJoinPoint joinPoint) {logger.warn("执行环绕通知之前:");try {joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}logger.warn("执行环绕通知之后");
}

通知方法中有一个ProceedingJoinPoint类型参数,通过其proceed方法来调用原方法。需要注意的是环绕通知是会覆盖原方法逻辑的,如果上面代码不执行joinPoint.proceed();这一句,就不会执行原被织入方法。因此环绕通知一定要调用参数的proceed方法,这是通过反射实现对被织入方法调用。
再次测试如下:
在这里插入图片描述

通知方法传参

上面每个通知方法是没有参数的。其实,通知方法是可以接受被织入方法的参数的。我们上述被织入方法参数就是一个User对象,因此通知方法也可以加上这个参数接受。我们改变前置通知方法如下:

/*** 前置通知:在被代理方法之前调用*/
@Before("log() && args(user)")
public void doBefore(User user) {logger.warn("调用方法之前:");logger.warn("接收到请求!");logger.warn("得到用户id:" + user.getId());
}

在这里插入图片描述
可见在注解后面加一个args选项,里面写参数名即可。

需要注意的是,通知方法的参数必须和被织入方法参数一一对应例如:

/*** 被织入方法* /
public void print(User user, int num) {...
}/*** 通知* /
@Before("log() && args(user, num)")
public void doBefore(User user, int num) {...
}

总结

AOP其实使用起来是个很方便的东西,大大降低了相关功能之间的耦合度,使得整个系统井井有条。
定义切面,然后定义切点,再实现切面逻辑(各个通知方法),就完成了一个简单的切面。

来源:https://juejin.cn/post/6999570632409088008

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

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

相关文章

HarmonyOS 请求相应HTTPS概览

1.HTTP概述 请求和响应 2.HTTP请求开发步骤 2.1.module.json5中添加 ohos.permission.INTERNET 2.2.导入http模块 2.3.创建htppt请求 2.4.发起请求 2.5.处理响应 2.6.销毁http对象 3.几个基本概念&#xff1a; 3.1.Webview&#xff1a;提供We…

博客建站4 - ssh远程连接服务器

1. 什么是SSH?2. 下载shh客户端3. 配置ssh密钥4. 连接服务器5. 常见问题 5.1. IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! 1. 什么是SSH? SSH&#xff08;Secure Shell&#xff09;是一种加密的网络协议&#xff0c;用于在不安全的网络中安全地远程登录到其他…

【React】项目的目录结构全面指南

文章目录 一、React 项目的基本目录结构1. node_modules2. public3. src4. App.js5. index.js6. .gitignore7. package.json8. README.md 二、React 项目的高级目录结构1. api2. hooks3. pages4. redux5. utils 三、最佳实践 在开发一个 React 项目时&#xff0c;良好的目录结构…

【微软蓝屏】微软Windows蓝屏问题汇总与应对解决策略

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【接口自动化_07课_Pytest+Excel+Allure完整框架集成_下】

目标&#xff1a;优化框架场景 1. 生成对应的接口关联【重点】 2. 优化URL基础路径封装【理解】 3. 利用PySQL操作数据库应用【理解】--- 怎么用python连接数据库、mysql 4. 通过数据库进行数据库断言【重点】 5. 通过数据库进行关联操作【重点】 一、接口关联&#xff1a…

项目打包与运行

前端运行时必须有与后端相同的数据库版本&#xff0c;数据库账号密码 右侧maven -> 展开要打包的项目 -> 生命周期 -> 双击package 打包好之后在target目录下 右键打开 在资源目录下输入cmd&#xff0c;执行以下命令即可运行&#xff08;端口号为yml文件…

ITK-中值滤波

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 中值滤波原理 中值滤波是一种常用的非线性滤波技术&#xff0c;用于去除图像中的噪声&#xff0c;特别是椒盐噪声和脉冲噪声。它…

邮件安全篇:邮件反垃圾系统运作机制简介

1. 什么是邮件反垃圾系统&#xff1f; 邮件反垃圾系统是一种专门设计用于检测、过滤和阻止垃圾邮件的技术解决方案。用于保护用户的邮箱免受未经请求的商业广告、诈骗信息、恶意软件、钓鱼攻击和其他非用户意愿接收的电子邮件的侵扰。 反垃圾系统的常见部署形式 2. 邮件反垃圾…

操作系统杂项(十)

目录 一、简述socket中select、epoll的使用场景和区别 1、使用场景 2、区别 二、epoll水平触发和边缘触发的区别 三、简述Reactor和Proactor模式 1、Reactor 2、Proactor 3、区别 四、简述同步和异步的区别&#xff0c;阻塞和非阻塞的区别 1、同步与异步 2、阻塞与非…

redis的使用场景

1. redis的使用场景 redis使用场景的案例&#xff1a;[1]热点数据的缓存[2]分布式锁[3]短信业务&#xff08;登录注册时&#xff09;2. redis实现注册登录功能 代码 在发送验证码时&#xff0c;先判断数据库是否有该手机号&#xff0c;有则发送验证码&#xff08;此时redis缓存…

vs code解决报错 (c/c++的配置环境 远端机器为Linux ubuntu)

参考链接&#xff1a;https://blog.csdn.net/fightfightfight/article/details/82857397 https://blog.csdn.net/m0_38055352/article/details/105375367 可以按照步骤确定那一步不对&#xff0c;如果一个可以就不用往下看了 目录 一、检查一下文件扩展名 二、安装扩展包并…

C#,.NET常见算法

1.递归算法 1.1.C#递归算法计算阶乘的方法 using System;namespace C_Sharp_Example {public class Program{/// <summary>/// 阶乘&#xff1a;一个正整数的阶乘Factorial是所有小于以及等于该数的正整数的积&#xff0c;0的阶乘是1&#xff0c;n的阶乘是n&#xff0…

WPF MVVM框架:CommunityToolkit.Mvvm包使用介绍

最近在需要使用MVVM框架的时候才发现MvvmLight作者宣布停止更新了&#xff0c;有点可惜。 原作者推荐使用CommunityToolkit.Mvvm包&#xff0c;所以这里做一个CommunityToolkit.Mvvm包使用的全面的总结。 开发环境&#xff1a; Visual Studio 2019Windows 10 1903CommunityTo…

四、GD32 MCU 常见外设介绍 (5) TIMER 模块介绍

5.1.TIMER 基础知识 TIMER分高级定时器&#xff0c;通用定时器L0&#xff0c;L1&#xff0c;L2和基本定时器。 5.2.硬件连接说明 TIMER 属于片内外设&#xff0c;对于外部硬件设计&#xff0c;只需要单独IO口外接信号线即可。 5.3.GD32 TIMER 外设原理简介&#xff08;以 G…

电脑屏幕录制软件,分享4款(2024最新)

在今天&#xff0c;我们的电脑屏幕成为了一个多彩多姿的窗口。通过它我们可以浏览网页、观看视频、处理文档、进行游戏……有时&#xff0c;我们想要记录下这些精彩瞬间&#xff0c;与朋友分享&#xff0c;或者作为教程留存&#xff0c;这时&#xff0c;电脑屏幕录制就显得尤为…

全年销售7亿块,巧克力企业如何通过相邻业务打造极致产品力?

蒂罗尔巧克力是日本经典的巧克力品牌。 糖果业务是松尾早期的主营业务&#xff0c;在主营业务下滑的情况下&#xff0c;确立新的竞争方向&#xff0c;通过主营业务优势进入相邻业务&#xff0c;打造新产品成就巧克力极致产品力&#xff0c;避免衰退重回增长。 如何通过进入相邻…

Ubuntu下载jdk:cannot execute binary file

虚拟机上Ubuntu系统安装jdk且配置环境之后&#xff0c;java -version显示cannot execute binary file&#xff0c;多番查阅推测是由于系统和jdk版本不兼容的原因。 uname -m查看系统版本位i686&#xff0c;是32位的&#xff0c;和64位的jdk版本不兼容。因此&#xff0c;下载32位…

Linux:core文件无法生成排查步骤

1、进程的RLIMIT_CORE或RLIMIT_SIZE被设置为0。使用getrlimit和ulimit检查修改。 使用ulimit -a 命令检查是否开启core文件生成限制 如果发现-c后面的结果是0&#xff0c;就临时添加环境变量ulimit -c unlimited&#xff0c;之后在启动程序观察是否有core生成&#xff0c;如果…

【学习笔记】解决Serial Communication Library编译问题

【学习笔记】解决编译 Serial Communication Library 时的 Catkin 依赖问题 Serial Communication Library 是一个用 C 编写的用于连接类似 rs-232 串口的跨平台库。它提供了一个现代的 C 接口&#xff0c;它的工作流程设计在外观和感觉上与 PySerial 相似&#xff0c;但串口速…

Axure软件新功能解析与应用技巧分享

Axure是一种用于创建原型和交互设计的软件工具&#xff0c;广泛应用于操作界面。&#xff08;UI&#xff09;和客户体验&#xff08;UX&#xff09;为了展示和测试应用程序、网站或其他数据产品的性能和操作界面&#xff0c;设计帮助产品经理、设计师和开发者制作具有交互性的原…