Spring AOP 和 Spring Boot 统一功能处理

news2024/11/16 2:15:40

文章目录

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

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

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

相关文章

Pycharm第三方库导入失败避坑!

最近遇到了明明安装了 python 第三方库&#xff0c;但是在 pycharm 当中却导入不成功的问题。 使用Pycharm手动安装三方库和自动安装三方库都失败&#xff0c;以及Pycharm终端使用pip命令安装也未解决。网上找各种方法尝试都没成功&#xff0c;原来是一不小心就跳进了虚拟环境…

【经验分享】openGauss 5.0.0全密态数据库应用小试

引子&#xff1a; 去年了解openGauss数据库安全特性的时候了解到全密态等只查询特性&#xff0c;实际上openGauss在早期的1.0.0版本就引入了全密态等值查询特性&#xff0c;下面尝试对openGauss 5.0.0版本全密态的使用进行记录&#xff0c;供参考。 全密态数据库&#xff1a;…

造型精致的冰精灵充电头,充电效率高安全可靠,居家出行皆可用

随着大家对手机的依赖度越来越高&#xff0c;快速充电已经成为必不可少的需求。快充当然少不了支持快充的充电器&#xff0c;现在市面上的快充头很多&#xff0c;安全性和便携性是我们选择时的重点关注方向&#xff0c;我目前用的是战飞ZEFi冰精灵&#xff0c;这款产品有着独特…

Python学习路线 - Python语言基础入门 - 函数进阶

Python学习路线 - Python语言基础入门 - 函数进阶 函数的多返回值多个返回值 函数的多种参数使用形式函数参数种类位置参数关键字参数缺省参数不定长参数位置传递 - 不定长关键字传递 - 不定长 函数作为参数传递lambda匿名函数 函数的多返回值 问: 如果一个函数如些两个return…

[Spring ~松耦合的设计神器]`SPI`

Java SPI&#xff08;Service Provider Interface&#xff09;是一种Java的服务提供者接口机制。它允许在运行时动态加载实现服务接口的类。 文章目录 基本概念最简单的实例使用 jar 包通过 spi动态实现接口功能 基本概念 SPI 机制的基本思想是&#xff0c;定义一个服务接口&a…

Git 使用教程(超级详细)

目录 一&#xff1a;Git二&#xff1a;SVN与Git的的区别三、安装Git四&#xff1a;常规操作五&#xff1a;远程仓库六&#xff1a;创建与合并分支七&#xff1a;bug分支八&#xff1a;多人协作九&#xff1a;git可视化工具 Git Git 是一种分布式版本控制系统&#xff0c;用于…

Echarts 热力图与折线图的结合

热力图与折线图结合使用(文末含源码) 这种需求并不多见&#xff0c;遇到后第一时间翻看了Echars官方文档&#xff0c;并没有发现类似的例子。于是自己动手合并了双轴&#xff0c;后发现折线图会被遮盖。经过排查发现了一个关键参数&#xff1a;visualMap的配置。这个配置在热力…

Vue脚手架 Vue CLI安装

目录 0.为什么要安装Vue CLI脚手架 1.配置方法 1.全局安装 (一次) 2.查看Vue版本&#xff08;一次&#xff09; 报错&#xff1a;出现禁止运行脚本 3.创建项目架子&#xff08;可多次&#xff09; 报错npm err! 问题&#xff1a;已知npm换过国内源&#xff0c;且进度条…

了解AOP

1 AOP概述 思考&#xff1a;在一个教务系统中&#xff0c;以下哪些是主要业务逻辑&#xff0c;哪些是次要业务逻辑&#xff1f; 1.1 关于AOP AOP&#xff08;面向方面/切面编程&#xff09;是对OOP&#xff08;面向对象编程&#xff09;的补充&#xff0c;提供另一种关于程序…

FC 可视化功能菜单主代码

[FC][BestVisible][Config].asm ;文件头配置 NES_16KB_PRG_SIZE 8 NES_8KB_CHR_SIZE 16 PRG_BANK_MASK NES_16KB_PRG_SIZE * 2 - 1 ;bank号掩码 PRG_BANK_E000 NES_16KB_PRG_SIZE * 2 - 1 PRG_BANK_C000 …

《Kotlin核心编程》笔记:集合、序列与内联函数

集合的高阶函数API map 操作 val list listOf(1, 2, 3, 4, 5, 6) val newList list.map { it * 2 }当然&#xff0c;在 Java 8 中&#xff0c;现在也能像Kotlin那样去操作集合了。 上面的方法实际上就是一个高阶函数&#xff0c;它接收的参数实际上就是一个函数&#xff0…

sourcetree使用详解

介绍 SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端管理工具&#xff0c;同时也是Mn版本控制系统工具。支持创建、克隆、提交、push、pull 和合并等操作。——百度百科 是一款比较好用的图形化GUI的git、hg管理工具。还有一些其他的可视化代码管理工具&#x…

【webstrom】【idea】修改git历史提交记录

webstrom修改git历史提交记录 历史记录中有3条提交记录 此时2中的提交记录需要更新&#xff0c;我们可以在2中右击&#xff0c;选择“从这里执行交互式变基” 在弹框中选择需要修改提交记录2右击&#xff0c;然后选择“停止以编辑” 启动变基 更改2中内容 提交对2的更改 …

美国 AGU 发布 AI 应用手册,明确 6 大指导方针

爆发性的 AI 应用&#xff1a;风险与机遇并存 在空间和环境科学领域&#xff0c;AI 工具的应用越来越广泛——诸如天气预报和气候模拟&#xff0c;能源及水资源管理等等。可以说&#xff0c;我们正在经历前所未有的 AI 应用爆发&#xff0c;面对其中的机遇与风险&#xff0c;更…

《PySpark大数据分析实战》-11.Spark on YARN模式安装Hadoop

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

Power BI - 5分钟学习增加索引列

每天5分钟&#xff0c;今天介绍Power BI增加索引列。 什么是增加索引列&#xff1f; 增加索引列就是向表中添加一个具有显式位置值的新列&#xff0c;一般从0或者从1开始。 举例&#xff1a; 首先&#xff0c;导入一张【Sales】样例表(Excel数据源导入请参考每天5分钟第一天)…

目前最火的大模型训练框架 DeepSpeed 详解来了

目前&#xff0c;大模型的发展已经非常火热&#xff0c;关于大模型的训练、微调也是各个公司重点关注方向&#xff0c;但是大模型训练的痛点是模型参数过大&#xff0c;动辄上百亿&#xff0c;如果单靠单个GPU来完成训练基本不可能。所以需要多卡或者分布式训练来完成这项工作。…

Android 移动端编译 cityhash动态库

最近做项目&#xff0c; 硬件端 需要 用 cityhash 编译一个 动态库 提供给移动端使用&#xff0c;l 记录一下 编译过程 city .cpp // // Created by Administrator on 2023/12/12. // // Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charg…

Win11黑屏死机怎么办?

Win11黑屏死机是一个令人烦恼的问题&#xff0c;特别是对于那些计算机知识并不充裕的人来说。那么Win11死机黑屏怎么办呢&#xff1f;下面我们就来了解一下。 方案一&#xff1a;卸下外部硬盘驱动器 有些时候&#xff0c;电脑的外部硬件可能会导致电脑黑屏问题。在这种情况下&…

【️Java和C++主要的区别有哪些?各有哪些优缺点?】

✅Java和C主要的区别有哪些&#xff1f;各有哪些优缺点&#xff1f; ✅Java和C分别代表两种类型的语言✅ C是编译型语言✅ Java是解释型语言✅ 两者更多的主要区别如下&#xff1a; ✅知识拓展✅Java与C的参数方法有什么区别&#xff1f; ✅Java和C分别代表两种类型的语言 Java…