代码编织梦想

拦截器

什么是拦截器

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。

如何自定义拦截器

自定义一个拦截器非常简单,只需要实现HandlerInterceptor这个接口即可,这个接口有三个可实现的方法

  1. preHandle()方法:该方法会在控制器方法前执行,其返回值表示是否知道如何写一个接口。中断后续操作。当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。

  2. postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。

  3. afterCompletion()方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。

如何让拦截器在Spring Boot中生效

想要在Spring Boot生效其实很简单,只需要定义一个配置类,实现WebMvcConfigurer这个接口,并且实现其中的addInterceptors()方法即可,代码如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private XXX xxx;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 不拦截的uri
        final String[] commonExclude = {}};
        registry.addInterceptor(xxx).excludePathPatterns(commonExclude);
    }
}

用拦截器规避重复请求

需求

开发中可能会经常遇到短时间内由于用户的重复点击导致几秒之内重复的请求,可能就是在这几秒之内由于各种问题,比如网络事务的隔离性等等问题导致了数据的重复等问题,因此在日常开发中必须规避这类的重复请求操作,今天就用拦截器简单的处理一下这个问题。

思路

在接口执行之前先对指定接口(比如标注某个注解的接口)进行判断,如果在指定的时间内(比如5秒)已经请求过一次了,则返回重复提交的信息给调用者。

根据什么判断这个接口已经请求了?

根据项目的架构可能判断的条件也是不同的,比如IP地址用户唯一标识请求参数请求URI等等其中的某一个或者多个的组合。

这个具体的信息存放在哪?

由于是短时间内甚至是瞬间并且要保证定时失效,肯定不能存在事务性数据库中了,因此常用的几种数据库中只有Redis比较合适了。

实现

Docker启动一个Redis

docker pull redis:7.0.4

docker run -itd \
	--name redis \
	-p 6379:6379 \
	redis:7.0.4

创建一个Spring Boot项目

使用idea的Spring Initializr来创建一个Spring Boot项目,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSz8yzFy-1669041488871)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6c41a65f5d08405e9382150f8e23ffe7~tplv-k3u1fbpfcp-watermark.image?)]

添加依赖

pom.xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>springboot_06</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_06</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--spring redis配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!-- 1.5的版本默认采用的连接池技术是jedis  2.0以上版本默认连接池是lettuce, 在这里采用jedis,所以需要排除lettuce的jar -->
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置Redis

application.properties

spring.redis.host=127.0.0.1
spring.redis.database=1
spring.redis.port=6379

定义一个注解

package com.example.springboot_06.intercept;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /**
     * 默认失效时间5秒
     *
     * @return
     */
    long seconds() default 5;
}

创建一个拦截器

package com.example.springboot_06.intercept;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 重复请求的拦截器
 *
 * @Component:该注解将其注入到IOC容器中
 */
@Slf4j
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {

    /**
     * Redis的API
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * preHandler方法,在controller方法之前执行
     * <p>
     * 判断条件仅仅是用了uri,实际开发中根据实际情况组合一个唯一识别的条件。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            // 只拦截标注了@RepeatSubmit该注解
            HandlerMethod method = (HandlerMethod) handler;
            // 标注在方法上的@RepeatSubmit
            RepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RepeatSubmit.class);
            // 标注在controler类上的@RepeatSubmit
            RepeatSubmit repeatSubmitByCls = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RepeatSubmit.class);
            // 没有限制重复提交,直接跳过
            if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByCls)) {
                log.info("isNull");
                return true;
            }

            // todo: 组合判断条件,这里仅仅是演示,实际项目中根据架构组合条件
            //请求的URI
            String uri = request.getRequestURI();

            //存在即返回false,不存在即返回true
            Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "",
                    Objects.nonNull(repeatSubmitByMethod) ? repeatSubmitByMethod.seconds() : repeatSubmitByCls.seconds(), TimeUnit.SECONDS);

            //如果存在,表示已经请求过了,直接抛出异常,由全局异常进行处理返回指定信息
            if (ifAbsent != null && !ifAbsent) {
                String msg = String.format("url:[%s]重复请求", uri);
                log.warn(msg);
                // throw new RepeatSubmitException(msg);
                throw new Exception(msg);
            }
        }
        return true;
    }
}

配置拦截器

package com.example.springboot_06.config;
import com.example.springboot_06.intercept.RepeatSubmitInterceptor;
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 WebConfig implements WebMvcConfigurer {

    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 不拦截的uri
        final String[] commonExclude = {"/error", "/files/**"};
        registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude);
    }
}

写个测试Controller

package com.example.springboot_06.controller;

import com.example.springboot_06.intercept.RepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 标注了@RepeatSubmit注解,全部的接口都需要拦截
 *
 */
@Slf4j
@RestController
@RequestMapping("/user")
@RepeatSubmit
public class UserController {

    @RequestMapping("/save")
    public ResponseEntity save() {
        log.info("/user/save");
        return ResponseEntity.ok("save success");
    }
}

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzbHqFbA-1669041488871)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/379d50f9afff46ad87516f80a5b6568b~tplv-k3u1fbpfcp-watermark.image?)]

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ln_ydc/article/details/127974163

springboot拦截器+注解方式实现防止表单重复提交_gavin_wangzg的博客-爱代码爱编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Gavin_wangzg/article/details/79738316 表单重复提交在web应用中是比较常见的问题,重复

springboot之handlerinterceptor拦截器的使用 ——(四)防重复提交_zhibo_lv的博客-爱代码爱编程

看本篇博客前应当先看完前面三篇,这一篇是基于前面三篇的知识点的整合。所以很多重复的代码这里就不写出了 后台通过拦截器和redis实现防重复提交,避免因为网络原因导致多次请求同时进入业务系统,导致数据错乱,也可以防止对外

springboot拦截器拦截ip_weixin_40650476的博客-爱代码爱编程

springboot拦截器拦截IP 新建MyWebMvcConfigurerAdapter 代码 去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片. import org.springf

在SpringBoot项目使用拦截器实现简单的登陆功能-爱代码爱编程

在SpringBoot项目使用拦截器实现简单的登陆功能 HandlerInterceptor是SpringWebMVC的拦截器,类似于Servlet开发中的过滤器Filter,用于对请求进行拦截和处理。可以应用如下场景: 1、权限检查:如检测请求是否具有登录权限,如果没有直接返回到登陆页面。 2、性能监控:用请求处理前和请求处理后的时间差计算整个请求响应

springboot 防止重复请求,防止重复点击-爱代码爱编程

  利用 springboot + redis 实现过滤重复提交的请求,业务流程如下所示,首先定义一个拦截器,拦截需要进行过滤的URL,然后用 session + URL 作为唯一 key,利用 redis setnx 命令,来判断请求是否重复,如果 key set 成功,说明非重复请求,失败则说明重复请求;   URL 拦截器可以使用 spring 拦截

java 拦截器配置文件_springBoot之配置文件的读取以及过滤器和拦截器的使用-爱代码爱编程

前言 在之前的学习springBoot中,成功的实现了Restful风格的基本服务。但是想将之前的工程作为一个项目来说,那些是仅仅不够的。可能还需要获取自定义的配置以及添加过滤器和拦截器。至于为什么将这些写在一起,只是因为这些比较简单而且也会经常用到,所以干脆就一起写出来了。 读取配置文件 在使用maven项目中,配置文件会放在resources

Springboot下使用拦截器(Interceptor)和 过滤器(Filter)-爱代码爱编程

Springboot下使用拦截器(Interceptor)和 过滤器(Filter) 1.拦截器(Interceptor) 1.1 工作原理 一个拦截器,只有preHandle方法返回true,postHandle、afterCompletion才有可能被执行;如果preHandle方法返回false,则该拦截器的postHandle、afterCo

SpringBoot中使用过滤器(filter)/监听器(listener)/拦截器(interceptorHandler)-爱代码爱编程

  在实际开发过程中,经常会碰见一些比如系统启动初始化信息、统计在线人数、在线用户数、过滤敏/高词汇、访问权限控制(URL级别)等业务需求。实现以上的功能,都会或多或少的用到过滤器、监听器、拦截器。 一.SpringBoot整合过滤器Filter 过滤器Filter,是Servlet的的一个实用技术了。可以通过过滤器,对请求进行拦截处理。 编写普

springboot 404 重复经过拦截器以及解决方法-爱代码爱编程

之前没注意springboot 404 会经过两次拦截器 我在拦截器做了token判断 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

SpringBoot之HandlerInterceptor拦截器的使用(四)防重复提交-爱代码爱编程

看本篇博客前应当先看完前面三篇,这一篇是基于前面三篇的知识点的整合。所以很多重复的代码这里就不写出了 后台通过拦截器和redis实现防重复提交,避免因为网络原因导致多次请求同时进入业务系统,导致数据错乱,也可以防止对外暴露给第三方的接口在业务尚未处理完的情况下重复调用。 1.首先引入fastjson <dependency>   

SpringBoot 使用拦截器、过滤器、监听器-爱代码爱编程

介绍 过滤器 过滤器的英文名称为 Filter, 是 Servlet 技术中最实用的技术。如同它的名字一样,过滤器是处于客户端和服务器资源文件之间的一道过滤网,帮助我们过滤掉一些不符合要求的请求,通常用作 Session 校验,判断用户权限,如果不符合设定条件,则会被拦截到特殊的地址或者基于特殊的响应。 首先需要实现 Filter接口

SpringBoot之HandlerInterceptor拦截器的使用-爱代码爱编程

SpringBoot之HandlerInterceptor拦截器的使用 ——(一)_“实用”是软件压倒一切的要素-CSDN博客_handlerinterceptorHandlerInterceptor简介拦截器我想大家都并不陌生,最常用的登录拦截、或是权限校验、或是防重复提交、或是根据业务像12306去校验购票时间,总之可以去做很多的事情。1、定义实现类定

SpringBoot 自定义拦截器并注册到容器中-爱代码爱编程

拦截器拦截的是 Controller层 写好的请求 如果自定义@WebServlet(urlPatterns = “/xxx”)是不会被拦截的,因为不是走的DispatcherServlet package com.atguigu.admin.controller; import com.atguigu.admin.bean.User; import

springboot如何避免sql注入漏洞呢?_qq_25073223的博客-爱代码爱编程

转自: SpringBoot如何避免SQL注入漏洞呢? 下文笔者讲述SpringBoot避免SQL注入漏洞的方法分享,如下所示 SQL盲注,SQL注入简介 SQL注入的风险: 数据库中的数据被任意查看,修改,删除 SQL注入的原因: 未对用户输入进行正确的验证 SQL注入如何避免 对危险字符进行过滤或sql参数化

基于springboot拦截器的aes报文解密-爱代码爱编程

从前到后实现一个 springboot 使用Incepter拦截器解析AES密文。 1.什么是AES加密 AES是一种对称加密,这个标准用来替代原先的DES(Data Encryption Standard),已经被多方分析且广为全世界所使用。本文中AES加密方法同样适用于 DES。 AES使用起来非常简单,前后端需要一个相同的密钥,前端加密完后,将