Spring AOP 和 Spring Boot 统一功能处理

文章目录

  • Spring AOP 是什么
  • 什么是 AOP
  • AOP 组成
    • 切面(Aspect)
    • 连接点(Join Point)
    • 切点(Pointcut)
    • 通知(Advice)
  • 实现 Spring AOP
    • 添加 Spring AOP 框架支持
    • execution表达式
    • 定义切面、切点、通知
  • Spring AOP 的原理
  • SpringBoot 统一功能处理
    • 拦截器
    • 统一异常处理
    • 统一数据返回格式

Spring AOP 是什么

Spring AOP(面向切面编程)是 Spring 框架的一个模块,用于支持切面编程。

什么是 AOP

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它旨在通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,以提高代码的模块性和可维护性。横切关注点包括那些在应用程序中散布在多个地方、不属于特定模块的功能,例如日志记录、事务管理、安全性检查等。

想象一下你在写一个软件应用程序,而这个应用程序中有一些功能是所有模块都需要用到的,比如日志记录、性能监测或者权限控制。Spring AOP就像是一个魔法工具,它允许你在不改变你原有代码的情况下,把这些共同的功能从各个地方剥离出来,形成一个独立的“切面”(Aspect)。

这个切面就像是一个横切的面,横切到你整个应用程序中的某些点。比如,你可以告诉 Spring AOP:“每次有人调用这个方法前,先执行一下这个日志记录的功能。” Spring AOP 就会在运行时,把这个日志记录的功能“插”到那个方法调用的前面,而不需要你去手动修改方法里的代码。

所以,Spring AOP 就是一个让你在不乱改原有代码的情况下,往程序中添加一些共同功能的工具。

AOP 组成

切面(Aspect)

切面(Aspect)包含了切点(Pointcut)和通知(Advice),它既包含了横切逻辑的定义,也包括了连接点的定义。

切面是包含了:通知、切点和切面的类,相当于 AOP 实现的某个功能的集合

连接点(Join Point)

应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

连接点相当于需要被增强的某个 AOP 功能的所有方法

切点(Pointcut)

Pointcut 是匹配 Join Point 的谓词。

Pointcut 的作用是提供一组规则(使用 AspectJ pointcut expression language 来描述)来匹配 Join Point,给满足规则的 Join Point 添加 Advice

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中一条一条的数据)

通知(Advice)

切面也是有目标的——它必须完成的工作。在 AOP 术语中,切面的工作被称为通知。

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

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

整个过程如图:

img

总结:

  1. 切面(Aspect):定义 AOP 业务类型的(当前 AOP 是做什么的)
  2. 连接点(Join Point):有可能调用 AOP 的地方
  3. 切点(Pointcut):定义 AOP 拦截规则
  4. 通知(Advice)[增强方法]:定义什么时候,干什么事。
    1. 前置通知:拦截的目标方法之前执行的通知(事件)
    2. 后置通知:拦截的目标方法之后执行的通知(事件)
    3. 返回之后通知:拦截的目标方法返回数据之后的通知
    4. 抛出异常之后的通知:拦截的目标方法抛出异常之后执行的通知
    5. 环绕通知:在拦截方法执行前后都执行的通知

实现 Spring AOP

添加 Spring AOP 框架支持

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

execution表达式

execution 是一种用于定义切点表达式的关键字。切点表达式决定了哪些方法将会被拦截并应用通知。

execution 表达式的基本语法如下:

execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
  • modifiers-pattern: 修饰符模式,表示方法的修饰符,如 publicprivateprotected 等。
  • return-type-pattern: 返回类型模式,表示方法的返回类型。
  • declaring-type-pattern: 声明类型模式,表示方法所属的类。
  • method-name-pattern: 方法名模式,表示方法的名称。
  • param-pattern: 参数模式,表示方法的参数。
  • throws-pattern: 异常模式,表示方法可能抛出的异常。

通配符 * 可以用于匹配任意字符。

通配符 .. (两个点),表示零个或多个元素的匹配。它可以用于表示任意数量的子包、任意数量的参数或任意数量的字符。

其中,带 ? 的非必需参数,不带 ? 的是必需参数,如 param-pattern 可以填 (..) 表示匹配任意参数列表的方法,不填,也就是 (),表示匹配无参的方法。不管你填不填,它都会发挥作用。而像 return-type-patternmethod-name-pattern 就都是必填的了。非必需参数被省略,就表示所有,相当于 *

execution 表达式示例:

  • execution(* com.example.service.*.*(..)): 匹配 com.example.service 包中所有类的所有方法。
  • execution(* save*(..)): 匹配所有以 “save” 开头的方法。
  • execution(public * *(..)): 匹配所有 public 修饰的方法。
  • execution(* com.example..*Service.*(..)): 匹配 com.example 包及其子包中所有类的以 “Service” 结尾的方法。

定义切面、切点、通知

@Component
@Aspect // 标识当前类为一个切面
public class LoginAOP {// 定义切点(拦截的规则)@Pointcut("execution(* org.example.mybatisdemo.controller.UserController.*(..))")public void pointcut() {}// 前置通知@Before("pointcut()")public void before() {System.out.println("执行了前置通知");}
}
package org.example.mybatisdemo.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getUsers")public void getUsers() {System.out.println("执行了 getUsers 方法");}
}

使用浏览器访问:http://localhost:8080/user/getUsers

控制台输出的结果:

执行了前置通知
执行了 getUsers 方法

前置通知成功在 getUsers 方法前执行了

其他通知就是用的注解不一样,不多赘述

环绕通知的方法体比较特殊

  • 环绕通知使用 ProceedingJoinPoint 参数来执行目标方法,并且可以决定是否继续执行目标方法或者在执行前后进行其他操作。
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {// 在目标方法执行之前的逻辑System.out.println("执行环绕通知的前置方法");// 执行目标方法Object result = joinPoint.proceed();// 在目标方法执行之后的逻辑System.out.println("执行环绕通知的后置方法");return result;
}

结果:

执行环绕通知的前置方法
执行了前置通知
执行了 getUsers 方法
执行了后置通知
执行环绕通知的后置方法
  • 环绕通知可以和前置通知后置通知同时存在,环绕通知的前后置方法会环绕在前后置通知的外侧。

Spring AOP 的原理

Spring AOP 是基于动态代理的实现。在运行时,Spring AOP 使用 JDK 动态代理或者 CGLIB(Code Generation Library)动态代理为目标对象生成代理对象,然后将切面织入代理对象中。这样,在调用目标对象的方法时,代理对象可以在适当的连接点上执行相关的通知逻辑。这种动态代理的方式实现了切面的透明织入,而不需要修改目标对象的源代码。

默认情况下,实现了接口的类,使用 AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类

织入(Weaving):代理的生成时机

织入指的是将切面的代码插入到应用程序的目标对象中的过程。

在AOP中,织入可以发生在以下三个阶段:

  1. 编译时织入(Compile-Time Weaving): 织入发生在源代码被编译成字节码的阶段。这需要特殊的编译器支持,例如使用 AspectJ编译器。在编译时织入时,切面的代码被直接编译到目标类的字节码中。
  2. 加载时织入(Load-Time Weaving): 织入发生在类加载的过程中。通过使用 Java 代理机制或者字节码增强技术,切面的代码被动态地织入到目标类中。在加载时织入时,目标类的字节码被增强,添加了切面的逻辑。
  3. 运行时织入(Runtime Weaving): 织入发生在应用程序运行的过程中。通过使用动态代理,切面的代码被动态地织入到目标对象中。Spring AOP通常采用运行时织入,使用JDK动态代理或CGLIB动态代理。

JDK 和 CGLIB 实现的区别

  1. 基于接口 vs. 基于类:

    • JDK 动态代理: JDK 动态代理是基于接口的。它要求目标对象必须实现一个或多个接口,然后使用 java.lang.reflect.Proxy 类生成代理对象。代理对象实现了目标接口,并将方法调用委托给 InvocationHandler 接口的实现类处理。
    • CGLIB: CGLIB(Code Generation Library)是基于类的。它通过继承目标类,并生成目标类的子类,来创建代理对象。因此,对于没有实现接口的类,CGLIB 也能生成代理。
  2. 代理对象生成方式:

    • JDK 动态代理: 使用 java.lang.reflect.Proxy 类和 InvocationHandler 接口。代理对象是在运行时动态生成的,基于接口的代理。
    • CGLIB: 使用字节码生成库,通过修改字节码生成目标类的子类。代理对象是在运行时生成的,基于类的代理。
  3. 性能:

    • JDK 动态代理: 由于基于接口,所以在代理的性能上通常比较高效。但仅支持对接口的代理,无法对类进行代理。
    • CGLIB: 由于生成子类,性能可能相对较低。但支持对类的代理,包括没有实现接口的类。
  4. 使用场景:

    • JDK 动态代理: 适用于要代理的类实现了接口的情况,更符合面向对象的设计原则。常用于 Spring AOP 的默认代理方式。
    • CGLIB: 适用于要代理的类没有实现接口的情况,或者想要对类进行代理。常用于一些第三方库,如 Hibernate。

SpringBoot 统一功能处理

拦截器

拦截器(Interceptor)是一种用于在请求处理的不同阶段执行额外逻辑的组件。拦截器可以用于拦截和处理 Spring MVC 中的 Web 请求,也可以用于拦截方法的执行,实现 AOP(Aspect-Oriented Programming)的一部分。

创建拦截器

接口定义: Spring 的拦截器接口是 HandlerInterceptor,其中包含了三个方法,分别表示在请求处理的不同阶段执行的逻辑:

  • preHandle(request, response, handler): 在处理请求之前被调用,返回值决定是否继续执行后续的处理器(Controller)。
  • postHandle(request, response, handler, modelAndView): 在处理请求后、视图渲染之前被调用,可以对ModelAndView进行修改。
  • afterCompletion(request, response, handler, ex): 在整个请求处理完成后被调用,通常用于资源清理工作。
package org.example.springbootaop.config;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;@Configuration
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 登录判断业务HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userinfo") != null) {return true;}response.setStatus(401);return false;}
}

配置拦截器:要使用拦截器,需要通过 InterceptorRegistry 配置类配置拦截器。指定在什么样的url下执行拦截器的方法

例:

package org.example.springbootaop.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MyConfig implements WebMvcConfigurer {@AutowiredLoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**") // 拦截所有请求.excludePathPatterns("/user/login")// 排除不拦截的 url.excludePathPatterns("/user/reg"); // 排除不拦截的 url}
}

访问 http://localhost:8080/user/getInfo,成功拦截并返回 401 状态码

img

统一异常处理

统一异常处理是一种通过一个中心化的机制来处理应用程序中发生的异常的方法。

使用 @ControllerAdvice + @ExceptionHandler 来实现

@ControllerAdvice:

  • @ControllerAdvice 是一个类级别的注解,用于定义一个全局控制器通知。
  • 通过 @ControllerAdvice 注解的类可以包含用于处理全局异常、全局数据绑定、全局数据预处理等的方法。

@ExceptionHandler:

  • @ExceptionHandler 是一个方法级别的注解,用于标识一个方法用于处理特定类型的异常。
  • 这个方法通常在 @ControllerAdvice 注解的类中定义,用于处理全局性的异常。
package org.example.springbootaop.config;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;@ControllerAdvice
public class ErrorAdvice {@ExceptionHandler(Exception.class)@ResponseBodypublic HashMap<String, Object> exAdvice(Exception e) {HashMap<String, Object> result = new HashMap<>();result.put("code", "-1");result.put("msg", e.getMessage());return result;}
}

使用@ControllerAdvice注解的类可以处理整个应用程序中所有@RequestMapping方法抛出的异常。

在上述代码中,exAdvice方法用于处理异常,并返回一个包含的错误信息。这个信息会显示到浏览器,以便用户能够得知发生了异常。

@ExceptionHandler(Exception.class) 表示处理所有异常。

统一数据返回格式

实现 ResponseBodyAdvice 接口来对 Controller 方法的返回值进行处理。

package org.example.springbootaop.config;import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.HashMap;@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true; // 在所有 Controller 的方法返回前进行拦截}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("msg", "");result.put("data", body);return result;}
}

这样,无论 Controller 的方法返回什么类型的数据,都会在最终返回给客户端之前经过 beforeBodyWrite 方法的处理,将其封装成统一的格式。

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

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

相关文章

Docker构建镜像时空间不足:/var/lib/docker,no space left on device

背景 在一次更新业务服务功能后&#xff0c;重新在服务器上构建微服务镜像&#xff0c;在构建镜像时报错空间不足&#xff1a; /var/lib/docker, no space left on device 赶紧用 df -h 看了下磁盘使用情况&#xff0c;果然&#xff0c; devicemapper 已经满了。。由于需要紧急…

使用Pytorch从零开始构建LoRA

引言 在这篇博文中&#xff0c;我将向大家展示如何使用Pytorch从头开始构建 LoRA。LoRA 是Low-Rank Adaptation或Low-Rank Adapters的缩写&#xff0c;它提供了一种高效且轻量级的方法来微调预先存在的语言模型。这包括BERT和RoBERTa等掩码语言模型&#xff0c;以及GPT、Llama…

基于循环神经网络长短时记忆(RNN-LSTM)的大豆土壤水分预测模型的建立

Development of a Soil Moisture Prediction Model Based on Recurrent Neural Network Long Short-Term Memory in Soybean Cultivation 1、介绍2、方法2.1 数据获取2.2.用于预测土壤湿度的 LSTM 模型2.3.土壤水分预测的RNN-LSTM模型的建立条件2.4.预测土壤水分的RNN-LSTM模型…

蓝桥杯专题-真题版含答案-【三角螺旋阵】【干支记年法】【异或加密法】【金字塔】

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

内网渗透测试基础——Windows PowerShell篇

内网渗透测试基础——Windows PowerShell篇 1. Windows PowerShell基础 Windows PowerShell是一种命令行外壳程序和脚本环境&#xff0c;它内置在每个受支持的Windows版本中&#xff08;Windows7、Windows Server 2008 R2及更高版本&#xff09;&#xff0c;为Windows命令行使…

怎么使用会声会影?2024年最新使用会声会影的具体步骤

一听说视频剪辑我们就不由得联想到电影、电视等一些高端的视频剪辑技术&#xff0c;大家都觉得视频剪辑是一个非常复杂而且需要很昂贵的设备才可以完成的技术活&#xff0c;这对很多“门外汉”来说都可望而不可及。实际上&#xff0c;使用会声会影剪辑视频不仅是很多人都可以操…

Docker单机部署OceanBase

文章目录 说明机器软硬件要求指导文档本次部署环境说明 OceanBase单机部署&#xff08;Docker&#xff09;一&#xff1a;拉取 OceanBase 数据库相关镜像二&#xff1a;启动 OceanBase 数据库实例完整启动日志展示 三&#xff1a;连接实例遇到报错&#xff1a;没有mysql客户端 …

php查询数据库,并通过表格展示

第一步&#xff1a;创建数据库 创建一个数据库php-crud 第二步&#xff1a;创建数据库表 在数据库php-crud下创建一个歌曲表song /*Navicat Premium Data TransferSource Server : MariaDBSource Server Type : MariaDBSource Server Version : 100605 (10.6.5-M…

Windows7下双网卡绑定(双网络冗余)

1.首先需要电脑主机里至少有两张网卡。 2.打开计算机管理&#xff0c;点击左侧的设备管理器&#xff1a; 3.点击展开右侧的 网络适配器&#xff1a; 4.如下是我们即将需要进行绑定的两张网卡&#xff1a; 5.右键点击第一张网卡&#xff0c;选择属性&#xff1a; 6.选择 分组 栏…

前端框架的虚拟DOM(Virtual DOM)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

C++试卷(华南理工大学)

华南理工大学期末考试 《高级语言程序设计&#xff08;I&#xff09;》A卷 注意事项&#xff1a; 1. 考前请将密封线内各项信息填写清楚&#xff1b; 2. 所有答案写在答题纸上&#xff0c;答在其它地方无效&#xff1b; 3&#xff0e;考试形式&#xff1a;闭卷&#xff1b…

分数约分-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第20讲。 分数约分&#xf…

Google Gemini Pro:AI模型的新里程碑,开放API访问;Octo: 一个开源通用的机器人策略

&#x1f989; AI新闻 &#x1f680; Google Gemini Pro&#xff1a;AI模型的新里程碑&#xff0c;开放API访问 摘要&#xff1a;Google宣布推出了名为Gemini的AI模型&#xff0c;旨在使AI更加有用。Gemini分为Ultra、Pro和Nano三个版本&#xff0c;并已开始在产品中使用。Ge…

TCP/IP详解——HTTPS 协议

文章目录 1. HTTPS 协议1.1 HTTPS 原理1.2 HTTPS 过程1.3 从数据包角度看 HTTPS 交互过程1.4 常见的 HTTPS 数据包解码1.4.1 ClientHello 数据包1.4.2 ServerHello 数据包 1.5 思考 1. HTTPS 协议 1.1 HTTPS 原理 HTTPS概念 HTTPS 是以安全为目标的HTTP通道&#xff0c;并不…

Node.js 工作线程与子进程:应该使用哪一个

Node.js 工作线程与子进程&#xff1a;应该使用哪一个 并行处理在计算密集型应用程序中起着至关重要的作用。例如&#xff0c;考虑一个确定给定数字是否为素数的应用程序。如果我们熟悉素数&#xff0c;我们就会知道必须从 1 遍历到该数的平方根才能确定它是否是素数&#xff…

RabbitMq基本使用

目录 SpringAMQP1.准备Demo工程2.快速入门1.1.消息发送1.2.消息接收1.3.测试 3.WorkQueues模型3.1.消息发送3.2.消息接收3.3.测试3.4.能者多劳3.5.总结 SpringAMQP 将来我们开发业务功能的时候&#xff0c;肯定不会在控制台收发消息&#xff0c;而是应该基于编程的方式。由于R…

Ubuntu安装蓝牙模块pybluez以及问题解决方案【完美解决】

文章目录 简介问题及解决办法总结 简介 近期因工程需要在Ubuntu中使用蓝牙远程一些设备。安装Bluetooth的Python第三方软件包pybluez时遇到很多问题&#xff0c;一番折腾后完美解决。此篇博客进行了梳理和总结&#xff0c;供大家参考。 问题及解决办法 pip install pybluez安…

【算法Hot100系列】最长回文子串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

NNDL 循环神经网络-梯度爆炸实验 [HBU]

目录 6.2.1 梯度打印函数 6.2.2 复现梯度爆炸现象 6.2.3 使用梯度截断解决梯度爆炸问题 【思考题】梯度截断解决梯度爆炸问题的原理是什么&#xff1f; 总结 前言&#xff1a; 造成简单循环网络较难建模长程依赖问题的原因有两个&#xff1a;梯度爆炸和梯度消失。 循环…

【MySQL】(DDL) 表操作-查询

查询&#xff1a; show tables ; //查询所有表名称 desc 表名称 ; //查询表结构 show create table 表名称; //查看创建表语句 create table 表名 ( 字段名1 字段类型1,字段名2 字段类型2) ; //创建表结构 示列&#xff1a; 1. show tables; use 数据库名; show tables …