SpringBoot 统一功能处理

一、用户登录拦截器

1、拦截器实现步骤

步骤1:自定义拦截器

// 自定义拦截器
@Component
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(AppVar.SESSION_KEY) != null) {
            // 返回 true -> 拦截器验证成功,继续执行后续的方法
            return true;
        }
        // 返回 false -> 拦截器验证失败,不会执行后续的目标方法
        return false;
    }
}

代码解析:

  1. 通过 @Component 注解将 LoginInterceptor 类标记为一个 Spring 组件,使其成为 Spring 容器中的一个可被管理的 Bean。

  2. LoginInterceptor 类实现了 Spring 提供的拦截器接口 HandlerInterceptor,并覆盖了其中的 preHandle 方法。preHandle 方法在目标方法执行前被调用。

  3. 在 preHandle 方法中,首先通过 HttpServletRequest 获取当前请求的 HttpSession 对象。如果 HttpSession 不为 null,且其中存储的 AppVar.SESSION_KEY 属性不为 null,表示用户已登录。

  4. 如果验证成功,即用户已登录,返回 true,表示拦截器验证通过,可以继续执行后续的目标方法;如果验证失败,即用户未登录,返回 false,表示拦截器验证失败,不会执行后续的目标方法。

步骤2:将自定义拦截器配置到系统设置中,并设置拦截规则

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    // 在系统配置中添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**");
    }
}

代码解析:

  1. 通过 @Autowired 注解将 LoginInterceptor 注入到 AppConfig 类中,该拦截器在上面已经定义好了。

  2. 在 addInterceptors 方法中,通过 registry.addInterceptor(loginInterceptor) 将 LoginInterceptor 拦截器添加到拦截器链中。

  3. 使用 addPathPatterns 方法设置需要拦截的 URL,这里使用 “/**” 表示拦截所有的请求。

  4. 使用 excludePathPatterns 方法设置不需要拦截的 URL,这些路由在拦截器中会被忽略。这里排除了一些静态资源和特定路径,比如登录页、注册页、CSS 文件、图片文件和 JavaScript
    文件。

2、拦截器实现原理

本质上 Spring 中的拦截器也是通过动态代理和环绕通知的 思想 来实现的。在拦截器中,可以通过实现 HandlerInterceptor 接口并重写 preHandlepostHandleafterCompletion 方法来实现环绕通知的功能。

通过阅读源码我们可以看到,在 Spring 中所有 Controller 的执行都会通过一个核心调度器 DispatcherServlet 来实现,所有的请求方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 方法中有一系列的事件处理方法,而在开始执行 Controller 中的目标方法 之前,会先调用预处理方法 applyPreHandle,在 applyPreHandle 方法中会获取所有拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法。如果拦截器中有一个返回了 false 那么后续的流程就不会执行了。

二、统一异常处理

通过使用 @RestControllerAdvice(@ControllerAdvice+@ResponseBody) 注解和 @ExceptionHandler 注解结合使用,可以实现全局的或是针对特定异常的统一异常处理,并将处理结果以统一的数据格式返回给客户端。

@RestControllerAdvice
public class ExceptionAdvice {
    // 仅限于空指针异常的异常处理
    @ExceptionHandler(NullPointerException.class)
    public ResultAjax doNullPointException(NullPointerException e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("异常"+e.getMessage());
        return resultAjax;
    }
    
    // 适用于所有异常的异常处理
    @ExceptionHandler(Exception.class)
    public ResultAjax doException(Exception e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("异常"+e.getMessage());
        return resultAjax;
    }
}

代码解析:

  1. @RestControllerAdvice 注解表示该类是一个全局控制器增强器,并且结合了 @ControllerAdvice 和 @ResponseBody 注解的功能。

  2. @ExceptionHandler 注解标注异常处理的方法。搭配 @RestControllerAdvice 注解可以在发生异常时统一处理异常并返回数据。

  3. doNullPointException 方法使用 @ExceptionHandler(NullPointerException.class) 注解来指定它处理的异常类型为
    NullPointerException。当发生空指针异常时,该方法会被调用。在方法体内,创建一个 ResultAjax
    对象并设置相应的错误信息,然后将其返回。

  4. doException 方法没有指定特定的异常类型,因此它将会处理所有类型的异常。当发生任何异常时,该方法会被调用。它的处理逻辑与 doNullPointException 方法类似,也是创建一个 ResultAjax 对象并设置错误信息,然后返回。

三、统一数据返回格式

统一数据的返回格式可以降低前端程序员和后端程序员的沟通成本,方便前端程序员更好的接收和解析后端数据接口返回的数据。

一般情况下,我们可以创建一个统一返回对象,提供一些成功和失败的返回接口,后续返回的数据直接调用接口即可返回约定的统一对象。具体实现如下:

// 定义统一返回对象
@Data
public class ResultAjax {
    // 状态码
    private int code;
    // 状态码的描述信息
    private String msg;
    // 返回数据
    private Object data;

    // 返回成功对象
    public static ResultAjax succ(Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg("");
        resultAjax.setData(data);
        return resultAjax;
    }
    public static ResultAjax succ(String msg, Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }
    // 返回失败对象
    public static ResultAjax fail(int code,String msg){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(null);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg,Object data){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }
}

之后业务中所有返回类型都设置为上述定义的统一返回对象:

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/hello")
    public ResultAjax sayHello(){
        return ResultAjax.succ("hello");
    }
    @RequestMapping("/hi")
    public ResultAjax sayHi(){
        return ResultAjax.succ("hi");
    }
}

当然虽然做出了上面的约定,但也不能保证在之后的业务代码不会误用其他返回类型,这个时候就需要使用到统一返回值的保底策略了。可以在 @ControllerAdvice 注解的类中实现 ResponseBodyAdvice 接口,对所有控制器方法的返回值进行统一的处理。

// 执行统一返回数据的保底策略
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    //     * true -> 才会调用 beforeBodyWrite 方法,
    //     * 反之则永远不会调用 beforeBodyWrite 方法
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        // 对返回值进行判断
        // 如果返回值和统一返回格式一致直接返回
        if (body instanceof ResultAjax) {
            return body;
        }
        // 对字符串返回格式进行单独判断处理
        if (body instanceof String) {
            ResultAjax resultAjax = ResultAjax.succ(body);
            try {
                return objectMapper.writeValueAsString(resultAjax);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        // 其他情况
        return ResultAjax.succ(body);
    }
}

代码解析:

  1. @ControllerAdvice 是一个注解,用于声明一个类为全局控制器增强器。在 @ControllerAdvice 注解的类中实现 ResponseBodyAdvice 接口,对所有控制器方法的返回值进行统一的处理
  2. 实现 supports 方法,判断当前返回类型是否需要进行响应体重写处理。由于这里返回 true,因此所有返回值都会被拦截并进行响应体重写处理。
  3. 实现 beforeBodyWrite 方法,对所有返回值进行统一的响应体处理。

四、@ControllerAdvice 实现原理(了解)

通过上面统一异常处理和统一数据返回格式的介绍,我们发现二者都使用到了 @ControllerAdvice 这个注解,下面我们简单介绍一下它的底层是怎么实现的:

@ControllerAdvice 它更像是一个全局的拦截器,可以对控制器的行为进行统一的处理和管理:

当我们点击 @ControllerAdvice 的源码,可以看到 @ControllerAdvice 同样派生于 @Component 组件,而所有组件初始化都会调用 InitializingBean 接口,其中 Spring MVC 中的实现的子类中有一个 afterPropertiesSet() 方法,表示所有的参数设置完成之后执行的方法,这个方法中又有一个 initControllerAdviceCache 方法,当程序执行到特定事件发生的时候,比如返回数据前或发生异常时,Spring会根据规则查找所有使用了@ControllerAdvice 注解的类,并调用其中对应的 Advice 方法来执行相应的业务逻辑。

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

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

相关文章

信号的机制——信号的发送与处理

对于硬件触发的,无论是中断,还是信号,肯定是先到内核的,然后内核对于中断和信号处理方式不同。一个是完全在内核里面处理完毕,一个是将信号放在对应的进程 task_struct 里信号相关的数据结构里面,然后等待进…

紫色调城市和奔跑人物剪影背景工会工作总结汇报PPT模板

这是一套紫色调城市和奔跑人物剪影背景工会工作总结汇报PPT模板,共33页; PPT模板封面,使用了蓝天白云、城市剪影、奔跑人物剪影背景图片。中间填写工会工作总结汇报PPT标题。界面色彩丰富充满活力。 PowerPoint模板内容页,由31张…

gittee启动器

前言 很多小伙伴反馈不是使用gitee,不会寻找好的项目,在拿到一个项目不知道从哪里入手。 鼠鼠我呀就是宠粉,中嘞,老乡。整!!! git的基本指令 在使用gitee的时候呢,我们只需要记住…

C++加持让python程序插上翅膀——利用pybind11进行c++和python联合编程示例

目录 0、前言1、安装 pybind11库c侧python侧 2、C引入bybind11vs增加相关依赖及设置cpp中添加头文件及导出模块cpp中添加numpy相关数据结构的接收和返回编译生成dll后改成导出模块同名文件的.pyd 3、python调用c4、C引入bybind11 0、前言 在当今的计算机视觉和机器学习领域&am…

【入门篇】1.4 redis 客户端 之 Lettuce 详解

文章目录 1. 简介1. 什么是Lettuce2. Lettuce与其他Redis客户端的比较3. Lettuce的特性和优势 2. 安装和配置3. 连接池配置1. 什么是连接池2. Lettuce的连接池使用与配置3. 连接池配置项 4. 基本操作1. 如何创建Lettuce连接2. Lettuce的基本操作如增删改查3. Lettuce的事务操作…

vue --version无法显示,只弹出vs窗口

参考连接: nodejs环境配置(解压包)安装教程_nodejs解压版安装及环境配置_tubond的博客-CSDN博客 原因:环境没搞好,没有设置全局文件夹,node默认放在C盘了,C盘有权限。因为npm -i vue/cli创建…

Vite - 配置 - 文件路径别名的配置

为什么要配置别名 别名的配置,主要作用是为了缩短代码中的导入路径。例如有如下的项目目录: project-name| -- src| -- a| --b| --c| --d| --e| -- abc.png| -- index.html| -- main.js如果想在 main.js 文件中使用 abc.png ,则使用的路径是 &#xff1…

【智能家居项目】FreeRTOS版本——多任务系统中使用DHT11 | 获取SNTP服务器时间 | 重新设计功能框架

🐱作者:一只大喵咪1201 🐱专栏:《智能家居项目》 🔥格言:你只管努力,剩下的交给时间! 目录 🍓多任务系统中使用DHT11🍅关闭调度器🍅使用中断 &am…

人类智能的精髓超出了统计概率

处理不确定性好坏的程度是衡量各种智能系统高低的一个重要指标。在处理不确定性时,智能系统需要具备推理、学习和决策的能力,通常使用概率和统计等方法来建模和处理不确定性,以便更好地应对现实世界中的复杂问题。统计概率是基于大量观察和数…

【Spring总结】基于配置的方式来写Spring

本篇文章是对这两天所学的内容做一个总结,涵盖我这两天写的所有笔记: 【Spring】 Spring中的IoC(控制反转)【Spring】Spring中的DI(依赖注入)Dependence Import【Spring】bean的基础配置【Spring】bean的实…

一键云端,AList 整合多网盘,轻松管理文件多元共享!

hello,我是小索奇,本篇教大家如何使用AList实现网盘挂载 可能还是有小伙伴不懂,所以简单介绍一下哈 AList 是一款强大的文件管理工具,为用户提供了将多种云存储服务和文件共享协议集成在一个平台上的便利性。它的独特之处在于&am…

彩色年终工作总结汇报PPT模板下载

这是一套彩色年终工作总结汇报PPT模板,共27页; PPT模板封面,使用了红黄蓝色块、网格背景。中间填写年终工作总结汇报PPT标题。界面为简约商务风格。 PowerPoint模板内容页,由25张彩色动态幻灯片图表,搭配PPT文字排版…

Python 双门双向门禁控制板实时监控源码

本示例使用设备:实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) #python通过缩进来表示代码块,不可以随意更改每行前面的空白,否则程序会运行错误!!!如果缩进不一致&a…

Python---函数练习:编写一个打招呼程序

函数的定义-------相关链接:Python---函数的作用,定义,使用步骤(调用步骤)-CSDN博客基本语法: def 函数名称([参数1, 参数2, ...]):函数体...[return 返回值] 函数的调用 Python中,函数和变量一…

2023年【危险化学品经营单位安全管理人员】考试题及危险化学品经营单位安全管理人员模拟试题

题库来源:安全生产模拟考试一点通公众号小程序 危险化学品经营单位安全管理人员考试题是安全生产模拟考试一点通总题库中生成的一套危险化学品经营单位安全管理人员模拟试题,安全生产模拟考试一点通上危险化学品经营单位安全管理人员作业手机同步练习。…

MSYS2介绍及工具安装

0 Preface/Foreword 1 MSYS2 官网:MSYS2

服务容错之限流之 Tomcat 限流 Tomcat 线程池的拒绝策略

在文章开头,先和大家抛出两个问题: 每次提到服务限流为什么都不考虑基于 Tomcat 来做呢?大家有遇到过 Tomcat 线程池触发了拒绝策略吗? JUC 线程池 在谈 Tomcat 的线程池前,先看一下 JUC 中线程池的执行流程&#x…

[acwing周赛复盘] 第 94 场周赛20230311

[acwing周赛复盘] 第 94 场周赛20231118 总结5295. 三元组1. 题目描述2. 思路分析3. 代码实现 5296. 边的定向1. 题目描述2. 思路分析3. 代码实现 六、参考链接 总结 好久没做acw了,挺难的。T1 模拟T2 前缀和以及优化。T3 贪心 5295. 三元组 链接: 5295. 三元组…

操作系统(存储管理进程管理设备管理)

文章目录 存储管理页式存储管理概念优点缺点页面置换算法快表(很快速的页表) 段式存储管理概念优点缺点 段页式存储管理概念优点缺点 进程管理概述作用特征功能分类计算机启动基本流程 进程管理进程的组成进程的基础状态前趋图进程资源图同步和互斥信号量…

os.path.join函数用法

os.path.join()是Python中用于拼接文件路径的函数,它可以将多个字符串拼接成一个路径,并且会根据操作系统的规则自动使用合适的路径分隔符。 注:Linux用的是/分隔符,而Windows才用的是\。 该函数属于os.path模块,因此在…