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 {
    @Override
    public 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 {
    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public 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)
    @ResponseBody
    public 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 {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true; // 在所有 Controller 的方法返回前进行拦截
    }

    @Override
    public 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 方法的处理,将其封装成统一的格式。

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

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

相关文章

初识SpringSecurity

目录 前言 特点 快速开始 导入依赖 运行项目 访问服务 权限控制 实现UserDetails接口 添加SecurityConfig配置类 测试接口DemoController 设置权限控制authorizeHttpRequests 结果分析 总结 前言 Spring Security是一个强大且高度可定制的身份验证和访问控制框架…

labelme标注json文件检查标注标签(修改imageWidth,imagePath,imageHeight)

# !/usr/bin/env python # -*- encoding: utf-8 -*- #---wzhimport os import json# 这里写你自己的存放照片和json文件的路径 json_dir =rC:\Users\Lenovo\Desktop\json3 json_files = os.listdir(json_dir

MBA-数学题概念和公式

{}公差大于零的等差数列:多个数字组成的数列&#xff0c;两两之间差相等,且后值减前值大于0&#xff0c;如&#xff1a;{-2,0,2,4}为公差数列为2的等差数列.因数是指整数a除以整数b(b≠0) 的商正好是整数而没有余数&#xff0c;10的因数为 2和5圆柱体表面积 2πr 2πrh球体表名…

【LeetCode刷题】--157.用Read4读取N个字符

157.用Read4读取N个字符 /*** The read4 API is defined in the parent class Reader4.* int read4(char[] buf4);*/public class Solution extends Reader4 {/*** param buf Destination buffer* param n Number of characters to read* return The number of actual…

天猫数据分析(天猫数据查询平台):11月天猫啤酒市场销售数据分析报告

在酒类市场中&#xff0c;被视作“气氛担当”的啤酒&#xff0c;是派对聚会或者自饮场景中的常客&#xff0c;消费人群广泛&#xff0c;如今&#xff0c;啤酒市场已进入存量时代&#xff0c;市场中啤酒的销售也在稳步增长。 鲸参谋数据显示&#xff0c;今年11月份&#xff0c;天…

LeetCode(64)分隔链表【链表】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 分隔链表 1.题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示…

C# WPF上位机开发(日志调试)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 程序开发的过程中&#xff0c;调试肯定是少不了的。比如说&#xff0c;这个时候&#xff0c;我们可以设置断点、查看变量、检查函数调用堆栈等等。…

克隆虚拟环境

conda虚拟环境 克隆clone 在服务器上想要使用别人搭好的环境&#xff0c;但是又怕自己对环境的修改更新会影响他人的使用&#xff0c;这个时候可以使用conda命令进行复制环境。 首先假设已经安装了Anaconda。 根据已有环境名复制生成新的环境 1、假设已有环境名为A&#xff0c…

面向对象三大特征——多态

目录 1. 多态 1.1 概述 1.2 多态中方法的访问特点 1.3多态中成员变量访问特点 1.4 多态中静态方法的访问特点 1.5 向上或向下转型 1.6多态的好处 2.抽象类 2.1抽象类 2.2抽象方法 2.3抽象类的特点 2.4抽象类成员特点 3.接口 3.1接口的概述 3.2接口中成员的特点 …

CANoe出现Busoff后如何恢复

项目场景&#xff1a; 在测试Busoff或者ECU进行快速上下电测试时&#xff0c;CANOE往往会进入Busoff状态&#xff0c;DUT会自动恢复&#xff0c;但CANoe只有手动重启CANOE&#xff0c;从而导致自动化测试无法进行下去。这时可以通过CAPL检测到Busoff发生时使用特定的函数重启C…

Python虚拟环境指南:告别依赖地狱

一、背景 在SAAS&#xff08;软件即服务&#xff09;平台中&#xff0c;用户使用自行定制的Python脚本已经成为司空见惯的做法&#xff0c;然而&#xff0c;由于不同用户对Python三方库的需求各不相同&#xff0c;而底层服务器一般只安装了一个Python版本。举例来说&#xff0…

【数据结构第 6 章 ④】- 用 C 语言实现图的深度优先搜索遍历和广度优先搜索遍历

目录 一、深度优先搜索 1.1 - 深度优先搜索遍历的过程 1.2 - 深度优先搜索遍历的算法实现 二、广度优先搜索 2.1 - 广度优先搜索遍历的过程 2.2 - 广度优先搜索遍历的算法实现 和树的遍历类似&#xff0c;图的遍历也是从图中某一顶点出发&#xff0c;按照某种方法对图中所…

算法leetcode|92. 反转链表 II(rust重拳出击)

文章目录 92. 反转链表 II&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a;进阶&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 92. 反转链表 II&#xff1a; 给你单链表的…

基于itextpdf的java读取和更新pdf表单域字段值功能

基于itextpdf的java读取和更新pdf表单域字段值功能 执行结果为&#xff1a; Hello World! keytopmostSubform[0].Page1[0].qhjc[0] keytopmostSubform[0].Page1[0].qhmc[0] keytopmostSubform[0].Page1[0].cqzh[0] keytopmostSubform[0].Page1[0].fm_year[0] keytopmostSubf…

springboot整合vue,将vue项目整合到springboot项目中

将vue项目打包后&#xff0c;与springboot项目整合。 第一步&#xff0c;使用springboot中的thymeleaf模板引擎 导入依赖 <!-- thymeleaf 模板 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-t…

yolov5单目测距+速度测量+目标跟踪

要在YOLOv5中添加测距和测速功能&#xff0c;您需要了解以下两个部分的原理&#xff1a; 单目测距算法 单目测距是使用单个摄像头来估计场景中物体的距离。常见的单目测距算法包括基于视差的方法&#xff08;如立体匹配&#xff09;和基于深度学习的方法&#xff08;如神经网…

锂电池是什么

锂电池 电工电气百科 文章目录 锂电池前言一、锂电池是什么二、锂电池的类别三、锂电池的作用原理总结前言 锂电池相比其他类型的电池具有许多优点,包括高能量密度、长寿命、低自放电率和较低的内阻等。这些特性使它成为现代电子设备和电动交通工具中首选的能源储存技术。然而…

互联网加竞赛 python+opencv+机器学习车牌识别

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器学习的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;3分 该项目较为新颖&#xff0c;适…

热烈庆祝安徽普朗膜技术有限公司参加2024济南生物发酵展

公司自2004年注册成立以来主要业务领域主要有以乳酸、氨基酸、抗生素为主的发酵液的提取分离&#xff1b;醋、酱油发酵产品的产品升级&#xff0c;果汁、茶饮料等天然产物提取的除菌和澄清过滤&#xff1b;低聚木糖、低聚果糖、果葡糖浆、高果糖浆等过滤、纯化、浓缩&#xff1…

java SSM酒店客房管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM酒店客房管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…