拦截器以及统一功能的实现

news2024/11/18 7:47:55

目录

引言 

实现一个简单的拦截器

拦截器小结

统一访问前缀

 统一异常处理

统一返回参数

@ControllerAdvice


引言 

 HandlerInterceptor是Spring MVC框架提供的一个拦截器接口,它用于对请求进行拦截和处理。在Spring MVC中,拦截器可以用于实现一些通用的功能,比如日志记录、权限验证、请求参数预处理等。

HandlerInterceptor接口定义了三个方法:

1. preHandle:在进入Controller之前执行,返回值表示是否继续执行后续的拦截器和Controller。可以通过该方法进行一些前置处理,比如登录验证、权限检查等。

2. postHandle:在调用Controller之后,渲染视图之前执行。可以通过该方法对ModelAndView进行修改,或者将公共的模型数据添加到视图中。

3. afterCompletion:在整个请求完成后执行,包括视图渲染完成和响应已经返回。可以通过该方法进行一些资源清理操作,比如释放数据库连接、删除临时文件等。

需要注意的是,拦截器可以配置多个,按照配置的顺序依次执行。当某个拦截器的preHandle方法返回false时,后续的拦截器和控制器将不再执行,请求将直接返回。

在使用HandlerInterceptor时,需要配置拦截器并将其注册到Spring MVC的配置中。可以通过实现HandlerInterceptor接口来自定义拦截器,并在配置中进行注册。

拦截器的使用可以有效地实现横切关注点,比如登录验证、日志记录等,与AOP相比,拦截器更加适用于一些与请求相关的处理,而AOP更适合于业务逻辑解耦。

但是,在实际开发中,我们通常需要在多个应用程序中使用相同的功能,例如日志记录、异常处理、事务控制等。如果每个应用程序都编写自己的实现,将会导致代码重复、维护困难等问题。因此,Spring Boot 提供了一系列机制来实现统一功能,以便在不同的应用程序中共享代码和功能。

具体来说,Spring Boot 支持以下几种方式来实现统一功能

  1. 1 AOP

Spring Boot 中使用 AOP 实现统一功能的方式与普通 Spring 应用程序相同,可以通过配置切面类来实现日志记录、事务控制等功能。这篇文章中讲述的拦截器就可以实现切面编程

  1. 2. 过滤器

Spring Boot 支持使用过滤器来实现切面编程。我们可以使用 javax.servlet.Filter 接口定义一个过滤器,并在应用程序启动时自动注册该过滤器。

  1. 3. 拦截器

Spring Boot 还支持使用拦截器来实现切面编程。我们可以使用 org.springframework.web.servlet.HandlerInterceptor 接口定义一个拦截器,并在应用程序启动时自动注册该拦截器。

  1. 4. 控制器建议

控制器建议是 Spring Boot 中另一个实现统一功能的方式。我们可以使用 org.springframework.web.bind.annotation.ControllerAdvice 注解快速定义一个控制器建议类,并在其中实现统一处理请求异常、返回值等功能。

综上所述,Spring Boot 提供了多种方式来实现统一功能,这些机制使得代码重用和维护变得更加容易和高效。

拦截器在请求进入控制器之前或之后对请求进行预处理或后处理

实现一个简单的拦截器

1. 写一个拦截器类继承HandlerInterceptor,并且重写preHandle方法

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


/**1.这是一个普通的拦截器
 *
 * 判断用户登录;
 * 1。1继承HandleInterceptor;
 * 1.2.重写preHeadler
 * */
/**2.将拦截器添加到配置文件中,并且设置拦截的规则
 *
 * */
@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){//才开始我写了||怪不得会空指针异常,
            System.out.println("登陆成功");
            return true;
        }
        response.sendRedirect("/login.html");
        //response.setStatus(403);
        return false;

    }
}

这里如果失败 重定向页面到login.html,

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登陆页面</title>
</head>
<body>
<h1>
    登陆页面
</h1>
</body>
</html>

2. 将上述拦截器添加到系统配置文件中,并且设置拦截的规则

package com.example.demo.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 AppConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//这里也可以只写一个new LoginIntercepter;
                .addPathPatterns("/**")//拦截所有请求
                .excludePathPatterns("/user/login")//登陆不用拦
                .excludePathPatterns("/user/req")
                .excludePathPatterns("/**/*.html")//后缀是html
                .excludePathPatterns("login.html");

    }
}

3.这里写入登录注册类无需被拦截,写入另一个会被拦截的类来对比

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/getuser")
    public String getuser(){
        System.out.println("执行了getuser");
        return "getuser";

    }

    @RequestMapping("/login")//登录和注册不用被拦截
    public String login(){
        System.out.println("执行  login方法");
        return " login";
    }

    @RequestMapping("/reg")
    public String reg(){
        System.out.println("执行 reg");
        return "reg___";
    }

}

 此时当我们访问127.0.0.1:8080/user/getuser就会由于被拦截而跳转到login.html

拦截器小结

所有的controller请求都都会通过DispatcherServlet(调度器)拦截器。

通过调度器源码,我们发现调度器会先进行(拦截器)预处理,如果不满足就不要往下执行。然后执行controller中的业务。拦截器也是通过动态代理和环绕通知实现的

*调度器:当客户端发送请求时,请求首先到达调度器(DispatcherServlet)。调度器会根据配置的请求映射规则,将请求转发给对应的控制器。控制器接收请求后,根据具体的业务逻辑进行处理,并生成相应的响应结果。起到了请求分发和控制器调度的作用,使得请求能够被正确地路由到相应的控制器进行处理。就是起到了请求分发和控制器调度的作用,使请求能够正确路由到相应控制器

统一访问前缀

就在程序中输入 confiure它会自动提示你的,我们只需要重写他的方法,有时候根据路由访问不到,可能就是因为这个

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("/zhangsan",c -> true);
        /**我们把前面拦截器中的代码去掉,就可以根据/zhangsan/user/getuser去访问,
         *但是user/getuser是不可以的,有点疑问
         * */
        /**
         * 还可以在properties文件中server.servlet.context-path=/lisi
         * */

    }

 统一异常处理

在Spring Boot中,可以通过定义@ControllerAdvice注解的类来实现全局的异常处理。 @ControllerAdvice是一个增强的Controller,使用它可以实现三个方面的功能:

1. 全局异常处理
2. 全局数据绑定
3. 全局数据预处理


@ExceptionHandler是Spring框架提供的一种异常处理机制,用于捕获Controller中抛出的异常,并处理这些异常

通常,当Controller中的方法抛出异常时,Spring框架会将异常转换为HTTP响应中的错误码和错误消息(例如404 Not Found、500 Internal Server Error等)。使用@ExceptionHandler可以覆盖默认的异常处理逻辑,自定义如何处理异常,并返回更有意义的错误信息给客户端  


我们重点关注第一个方面,也就是全局异常处理。下面是实现全局异常处理的步骤:

1. 创建一个标注了@ControllerAdvice注解的类,该类需要使用@ExceptionHandler注解来处理异常。

2. 在@ExceptionHandler注解中指定要处理的异常类型。

3. 在@ExceptionHandler注解中编写具体的异常处理代码,比如返回错误信息或者跳转到错误页面等。


具体实现方法可以参考以下代码:

package com.example.demo.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// 1.用于定义全局的异常处理器和全局数据绑定规则。@ControllerAdvice只能处理当前应用上下文范围内的Controller中的异常。如果要跨应用程序上下文使用,则应考虑使用@RestControllerAdvice。
@ResponseBody
public class MyExHandle {
    @ExceptionHandler(Exception.class)//2.一种异常处理机制,用于捕获Controller中抛出的异常,并处理这些异常
    public HashMap<String,Object> nullException(NullPointerException e){
        HashMap<String,Object> result=new HashMap<>();
        result.put("code","-1");
        result.put("msg"," 空指针异常 "+e.getMessage());
        result.put("data",null);
        return result;


    }



}
    @RequestMapping("/login")//登录和注册不用被拦截
    public String login(){
        Object o=null;
        o.hashCode();//在这里我们想要有空指针异常的时候返回给前端一个值,所以定义了一个异常
        /**
         * {
         *     "msg": " 空指针异常 null",
         *     "code": "-1",
         *     "data": null
         * }*/
        System.out.println("执行  login方法");
        return " login";
    }

统一返回参数

/**统一数据格式的返回
 * 1.@ControllerAdvice
 * 2.实现ResponseBody 接口
 * 3.重写接口中的方法 supports和beforeBodyWrite
 * */ 

这里是初版和改进版但并不是最终版

package com.example.demo.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.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;


@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    /**此方法返回true,则执行下面的beforeBodyWrite方法;
     *false就不执行喽
     * */
    @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) {
        HashMap<String,Object> result=new HashMap<>();
        result.put("code",200);
        result.put("msg","");
        result.put("data",body);

        return result;
    }
}
  @RequestMapping("/getnum")
    public Integer getNumber(){
        System.out.println("哈哈哈哈");
        return new Random().nextInt(10);
        /**{"msg":"","code":200,"data":7}
         * */
    }

 java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String],

因为HashMapString是不兼容的类型所以会报错。如果需要将HashMap转换为字符串表示,仍然需要编写逻辑来进行相应的转换操作,例如使用StringBuilder工具类来构建字符串表示。


使用jackson

Jackson是一个流行的Java库,用于处理JSON格式的数据。它提供了一组功能强大的API,可以实现JSON和Java对象之间的转换。

在您的代码示例中,通过@Autowired注解将Jackson的ObjectMapper类注入到ResponseAdvice类中。这样可以在beforeBodyWrite方法中使用ObjectMapper对象将返回的响应数据转换为JSON格式。

在beforeBodyWrite方法中,首先创建一个HashMap对象result,并将code、msg和data放入其中。然后,根据body的类型进行判断:

  • 如果body是String类型,将result对象转换为JSON字符串,并返回;
  • 否则,直接返回result对象。

这样,在控制器方法返回的数据在经过ResponseAdvice处理后,都会按照统一的数据格式进行返回。

 
package com.example.demo.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;

/**统一数据格式的返回
 * 1.@ControllerAdvice
 * 2.实现ResponseBody 接口
 * 3.重写接口中的方法 supports和beforeBodyWrite
 *
 *
 * */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    /**
     * Jackson是一个流行的Java库,用于处理JSON格式的数据。它提供了一组功能强大的API,可以实现JSON和Java对象之间的转换。
     * */
    @Autowired
   private ObjectMapper objectMapper;



    /**此方法返回true,则执行下面的beforeBodyWrite方法;
     *false就不执行喽
     * */
    @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) {

        HashMap<String,Object> result=new HashMap<>();
        result.put("code",200);
        result.put(  "msg","");
        result.put("data",body);
        if(body instanceof String){
            //String转换的时候会报错
            try {
                return objectMapper.writeValueAsString(result);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }

        }

        return result;
    }
}

@ControllerAdvice

派生于@Component,用于定义全局的异常处理、统一数据处理等 。它的具体实现是通过组合多个注解和解析类来实现不同的功能。

@Controller注解通常用于标记处理请求的控制器类,处理业务逻辑并返回响应数据。例如,处理用户登录、查询数据等请求。
@ControllerAdvice注解通常用于对全局进行增强处理。例如,统一处理异常情况、预处理请求数据,统一数据格式等。

通过源码查看,这个方法在执行会查找所有的@ControllerAdvice类,这些类会被容器中,当发生某个事件时,调用相应的Advice方法,如:发生异常时调用异常地Advice方法实现;

本文介绍了统一用户登录权限的效验使用 WebMvcConfigurer + Handlerlnterceptor 来实现,统一异常处理使用@ ControllerAdvice +@ ExceptionHandler 来实现,统一返回值处理使用@ ControllerAdvice +实现ResponseBodyAdvice接口并且重写两个方法 来处理。 

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

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

相关文章

什么是t检验?

t检验&#xff08;t-test&#xff09;是一种统计方法&#xff0c;用于比较两组数据之间的平均值是否存在显著差异。它通常用于分析两组样本的平均值是否具有统计学上的显著性差异。t检验基于正态分布的假设&#xff0c;它计算两组数据之间的t值&#xff0c;然后通过与t分布表进…

YOLO目标检测——人脸口罩佩戴数据集【(含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;公共场所监控场景下的大密度人群检测是否佩戴口罩&#xff0c;以及戴口罩的人证比对&#xff08;安检刷脸不用摘口罩&#xff09;、手机解锁、刷脸考勤等身份认证场景。数据集说明&#xff1a;人脸口罩佩戴检测数据集&#xff0c;真实场景的高质量图片…

reactnative 底部tab页面@react-navigation/bottom-tabs

使用react-navigation/native做的页面导航和tab‘ 官网&#xff1a;https://reactnavigation.org/docs/getting-started 效果图 安装 npm install react-navigation/nativenpm install react-navigation/bottom-tabs封装tabbar.js import { View, StyleSheet, Image } from …

【JavaEE】死锁问题 -- 多线程篇(5)

死锁问题 1. 死锁是什么?2. 如何避免死锁? 1. 死锁是什么? 概念 死锁是这样一种情形: 多个线程同时被阻塞, 它们中的一个或者全部都在等待某个资源被释放, 由于线程被无限期的阻塞, 因此程序不能正常终止。 死锁的三种常见的场景 一个线程, 一把锁, 但是是不可重入锁, 该线程…

新手上路:学会使用SELinux保护你的系统

1 Selinux的介绍 SELinux是为了提高系统安全性的机制。 它对系统的每一个程序、文件都引入了安全上下文。安全上下文标签&#xff0c;用于唯一标识文件、进程和资源。这些标签包括了安全策略的信息&#xff0c;允许SELinux强制执行策略。 1.1 Selinux关闭状态下 getenforce …

个微多账号聚合聊天管理如何实现?

在日常工作中&#xff0c;我经常遇到以下问题&#xff1a; 1. 微信号众多&#xff0c;需要频繁切换设备和账号&#xff0c;导致工作效率低下。 2. 无法及时回复客户消息&#xff0c;客户体验不尽如人意。 3. 难以随时掌握员工与客户的沟通情况&#xff0c;导致员工沟通质量难…

C语言实现把程序中自定义的print( )函数改写为等价的递归函数

完整代码&#xff1a; //把以下程序的 print( )函数改写为等价的递归函数。 #include<iostream> using namespace std; void print(int w) {for(int i1;i<w;i) {for(int j1;j<i;j){cout<<i<<" ";}} } void myPrint(int w) {// 当 w 为 1 时…

动手学深度学习—使用块的网络VGG(代码详解)

目录 1. VGG块2. VGG网络3. 训练模型 1. VGG块 经典卷积神经网络的基本组成部分是下面的这个序列&#xff1a; 1.带填充以保持分辨率的卷积层&#xff1b; 2.非线性激活函数&#xff0c;如ReLU&#xff1b; 3.汇聚层&#xff0c;如最大汇聚层。 定义网络块&#xff0c;便于我…

【LeetCode】67. 二进制求和

1 问题 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例 1&#xff1a; 输入:a “11”, b “1” 输出&#xff1a;“100” 示例 2&#xff1a; 输入&#xff1a;a “1010”, b “1011” 输出&#xff1a;“10101” 2 答案 自己写…

【分类讨论】CF1834D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 这是个分类讨论题&#xff0c;一开始还以为是枚举什么的&#xff0c;发现根本枚举不了 注意到最终的答案就两种情况&#xff1a;区间包含 or 区间不包含 对于第一种情况&#xff0c;贡献的最大值就是mxlen - m…

【Arduino TFT】基于 ESP32 S7789 320x240 TFT实现的SD2 天气时钟

忘记过去&#xff0c;超越自己 ❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-10-21 ❤️❤️ 本篇更新记录 2023-10-21 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言&#x1f4dd;&#x1f64…

使用langchain-chatchat里,faiss库中报错: AssertionError ,位置:assert d == self.d

发生报错&#xff1a; AssertionError&#xff0c;发生位置&#xff1a;class_wrappers.py里 assert d self.d&#xff0c;假如输出语句&#xff0c;查看到是因为d和self.d维度不匹配造成&#xff0c;解决方式&#xff1a; 删除langchain-chatchat/knowledge_base里的info.db…

初识树结构和二叉树

一&#xff0c;树概念及结构 1.1树结构的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 注意&a…

嵌入式Linux中内存管理详解分析

Linux中内存管理 内存管理的主要工作就是对物理内存进行组织&#xff0c;然后对物理内存的分配和回收。但是Linux引入了虚拟地址的概念。 虚拟地址的作用 如果用户进程直接操作物理地址会有以下的坏处&#xff1a; 1、 用户进程可以直接操作内核对应的内存&#xff0c;破坏…

linux任务优先级

这篇笔记记录了linux任务&#xff08;指线程而非进程&#xff09;优先级相关的概念&#xff0c;以及用户态可以用来操作这些优先级的系统调用。 基本概念 调度策略 linux内核中的调度器为任务定义了调度策略&#xff0c;也叫调度类&#xff0c;每个任务同一时刻都有唯一的调…

Android Framework系列---输入法服务

Android Framework系列之输入法服务 本文基于Android R(11)&#xff0c;从Framework角度介绍Android输入法框架流程及常用调试方法。 写在前面 车载项目需要定制输入法&#xff0c;也有一些POC演示的项目使用原生比如LatinIME&#xff08;源码路径为/packages/inputmethods…

CVE-2019-9766漏洞实战

1.利用msf生成反向连接的shellcode 2.构造具有反弹shell的MP3文件 将上一步标记的部分替换脚本中的shellcode 3.运行脚本,生成恶意mp3文件 4.msf设置监听并运行exploit 5.打开恶意文件 6.攻击机已经获得shell 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教…

运行原理:eBPF 是一个新的虚拟机吗?

目录 背景 eBPF 虚拟机是如何工作的&#xff1f; BPF 指令是什么样的&#xff1f; eBPF 程序是什么时候执行的&#xff1f; 小结 背景 前面&#xff0c;我们从最简单的 Hello World 开始&#xff0c;带你借助 BCC 库从零开发了一个跟踪 openat() 系统调用的 eBPF 程序。…

Leetcode1839. 所有元音按顺序排布的最长子字符串

Every day a Leetcode 题目来源&#xff1a;1839. 所有元音按顺序排布的最长子字符串 解法1&#xff1a;滑动窗口 要找的是最长美丽子字符串的长度&#xff0c;我们可以用滑动窗口解决。 设窗口内的子字符串为 window&#xff0c;每当 word[right] > window.back() 时&…

喜讯!持安科技入选2023年北京市知识产权试点单位!

近日&#xff0c;北京市知识产权局发布了“2023年度北京市知识产权试点示范单位及2020年度北京市知识产权试点示范单位复审通过名单”名单。 经过严格的初审、形式审核和专家评审&#xff0c;北京持安科技有限公司入选“2023年北京市知识产权试点单位”。 北京市知识产权试点示…