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

news2024/9/25 9:38:14

文章目录

  • 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());

   @Override
   public 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 {

   @Autowired
   private 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.coloradmin.cn/o/1949814.html

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

相关文章

ESP32和mDNS学习

目录 mDNS的作用mDNS涉及到的标准文件组播地址IPv4 多播地址IPv6 多播地址预先定义好的组播地址 mDNS调试工具例程mDNS如何开发和使用注册服务查询服务 mDNS的作用 mDNS 是一种组播 UDP 服务&#xff0c;用来提供本地网络服务和主机发现。 你要和设备通信&#xff0c;需要记住…

PHP基础语法-Part1

脚本格式 PHP脚本以<?php开头&#xff0c;以?>结尾&#xff1b; PHP语句以分号结尾&#xff08;;&#xff09; PHP是解释型语言&#xff1b; 输入和输出 获取用户的输入&#xff1a; $input readline("input:"); echo $input; echo "input:";…

EEtrade:区块链是什么

区块链&#xff0c;这个近年来频繁出现在我们视野中的术语&#xff0c;已经从一个技术小众圈的词汇&#xff0c;逐渐演变为全球关注的焦点。从比特币的诞生&#xff0c;到如今在金融、供应链、物联网等领域的广泛应用&#xff0c;区块链技术正在深刻地改变着我们的生活。那么&a…

将YOLOv8模型从PyTorch的.pt格式转换为TensorRT的.engine格式

TensorRT是由NVIDIA开发的一款高级软件开发套件(SDK)&#xff0c;专为高速深度学习推理而设计。它非常适合目标检测等实时应用。该工具包可针对NVIDIA GPU优化深度学习模型&#xff0c;从而实现更快、更高效的运行。TensorRT模型经过TensorRT优化&#xff0c;包括层融合(layer …

ARCGIS PRO DSK GraphicsLayer创建文本要素

一、判断GraphicsLayer层【地块注记】是否存在&#xff0c;如果不存在则新建、如果存在则删除所有要素 Dim GraphicsLayer pmap.GetLayersAsFlattenedList().OfType(Of ArcGIS.Desktop.Mapping.GraphicsLayer).FirstOrDefault() 获取当前map对象中的GetLayer图层 Await Queue…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十六章 设备驱动IO控制

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

云计算安全扩展要求解读

云计算简介&#xff1a; 一种全新网络服务方式&#xff0c;将传统的以桌面为核 心的任务处理转变为以网络为核心的任务处理&#xff0c; 利用互联网实现自己想要完成的一切处理任务&#xff0c; 使网络成为传递服务、计算力和信息的综合媒 介&#xff0c;真正实现按需计算、多…

揭秘Django与Neo4j:构建智能知识图谱的终极指南

揭秘Django与Neo4j:构建智能知识图谱的终极指南 前言 图是一种用于对象之间的成对关系进行建模的数学结构。 它由两个主要元素组成:节点和关系。 节点:节点可以看作是传统数据库中的记录。每个节点代表一个对象或实体,例如一个人或一个地方。节点按标签分类,这有助于根…

自研点直播转码核心

1. 背景 视频转码是将视频文件经过解封装、解码、滤镜处理、编码、封装从而转换为另一个视频文件的过程&#xff0c;B站每天都有大量的视频原片上传后经过转码系统转换为多个不同分辨率。转换后的视频在画质接近原片的前提下会拥有更低的码率&#xff0c;因此会提高网络传输时的…

3.3-LSTM的改进

文章目录 1改进点1.1多层化1.2 dropout1.2.1具体概念1.2.2应该插入到LSTM模型的哪里 1.3权重共享 2改进之后的LSTMLM的代码实现2.1初始化2.2前向计算2.3反向传播 3相应的学习代码的实现4总结 1改进点 1.1多层化 加深神经网络的层数往往能够学习更复杂的模式&#xff1b;因此这…

定制化爬虫管理:为企业量身打造的数据抓取方案

在数据驱动的时代&#xff0c;企业如何高效、安全地获取互联网上的宝贵信息&#xff1f;定制化爬虫管理服务应运而生&#xff0c;成为解锁专属数据宝藏的金钥匙。本文将深入探讨定制化爬虫管理如何为企业量身打造数据抓取方案&#xff0c;揭秘其在海量信息中精准捕获价值数据的…

C++初阶学习——探索STL奥秘——标准库中的string类

1. 为什么学习string类&#xff1f; 在我们学习C语言的时候&#xff0c;有一个点是非常难处理的&#xff0c;那就是字符串&#xff0c;在我们对字符串访问&#xff0c;增删查改时都是非常不便的&#xff0c;所以我们封装了一个string类主要来处理字符串有关的问题 2. 标准库中…

Premiere简约手绘风格箭头标题文字动画素材MOGRT

18个简约手绘风格箭头标题文字动画 | Premiere Pro mogrt 具有可定制的颜色和可编辑的文本。包括手绘箭头和文本动画&#xff0c;非常适合在项目中添加动态指针、标记和方向指示器。非常适合信息图表、社交媒体内容、下三分之一、徽章和简约界面。 项目特点&#xff1a; 独特…

python机器学习8--网络

1.超文本传输协议HTTP GET 在实际开发应用程序时&#xff0c;一定都会利用WiFi网络进行连接&#xff0c;再通过HTTP的方式读入后台的数据&#xff0c;并下载和显示在用户的PC上。这靠的是网络服务的技术&#xff0c;也就是大家提到的Web Service。而与HTTP服务器交换数据有两种…

北斗卫星导航:改变出行方式的科技奇迹

随着科技的不断发展&#xff0c;人们的出行方式也发生了翻天覆地的变化。而现代导航技术在其中扮演着重要的角色&#xff0c;尤其是北斗卫星导航系统的出现&#xff0c;更是给出行方式带来了巨大的改变。北斗卫星导航系统作为全球最大的卫星导航系统之一&#xff0c;不仅改善了…

《Milvus Cloud向量数据库指南》——BGE-M3:多功能、多语言、多粒度的文本表示学习模型

引言 在自然语言处理(NLP)领域,随着大数据时代的到来,对文本信息的精准处理与高效检索成为了研究热点。BERT(Bidirectional Encoder Representations from Transformers)作为近年来NLP领域的里程碑式模型,以其强大的上下文理解能力在多项任务中取得了显著成效。然而,面…

一文看懂以太坊智能合约!

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 任何文章不要过度深思&#xff01; 万事万物都经不起审视&#xff0c;因为世上没有同样的成长环境&#xff0c;也没有同样的认知水平&#xff0c;更「没有适用于所有人的解决方案…

学习笔记之Java篇(0725)

p this 普通方法中&#xff0c;this总是指向调用该方法的对象。 构造方法中&#xff0c;this总是指向正要初始化的对象。 this&#xff08;&#xff09;调用必须重载的构造方法&#xff0c;避免相同地址初始化代码&#xff0c;但只能在构造方法中用&#xff0c;比企鹅必须位…

相关性模型-正态分布均值假设检验★★★

该博客为个人学习清风建模的学习笔记&#xff0c;部分课程可以在B站&#xff1a;【强烈推荐】清风&#xff1a;数学建模算法、编程和写作培训的视频课程以及Matlab等软件教学_哔哩哔哩_bilibili 目录 1双侧检验 2单侧检验 3t检验 4两个正态总体均值差的检验 5逐对比较法 …

Origin制作线性拟合回归图

选中数据&#xff0c;点下方散点图 调整散点颜色 在分析中打开线性拟合回归 添加文本 显示上轴