Spring Boot第七弹,别再问我拦截器如何配置了!!!
持续原创输出,点击上方蓝字关注我吧

前言
上篇文章讲了Spring Boot的WEB开发基础内容,相信读者朋友们已经有了初步的了解,知道如何写一个接口。
今天这篇文章来介绍一下拦截器在Spring Boot中如何自定义以及配置。
Spring Boot 版本
本文基于的Spring Boot的版本是2.3.4.RELEASE。
什么是拦截器?
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
如何自定义一个拦截器?
自定义一个拦截器非常简单,只需要实现HandlerInterceptor这个接口即可,该接口有三个可以实现的方法,如下:
preHandle()方法:该方法会在控制器方法前执行,其返回值表示是否知道如何写一个接口。中断后续操作。当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。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比较合适了。
如何实现?
第一步,先自定义一个注解,可以标注在类或者方法上,如下:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /**
     * 默认失效时间5秒
     */
    long seconds() default 5;
}
第二步,创建一个拦截器,注入到IOC容器中,实现的思路很简单,判断controller的类或者方法上是否标注了@RepeatSubmit这个注解,如果标注了,则拦截判断,否则跳过,代码如下:
/**
 * 重复请求的拦截器
 * @Component:该注解将其注入到IOC容器中
 */
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
    /**
     * Redis的API
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * preHandler方法,在controller方法之前执行
     *
     * 判断条件仅仅是用了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))
                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)
                throw new RepeatSubmitException();
        }
        return true;
    }
}
第三步,在Spring Boot中配置这个拦截器,代码如下:
@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);
    }
}
OK,拦截器已经配置完成,只需要在需要拦截的接口上标注@RepeatSubmit这个注解即可,如下:
@RestController
@RequestMapping("/user")
//标注了@RepeatSubmit注解,全部的接口都需要拦截
@RepeatSubmit
public class LoginController {
    @RequestMapping("/login")
    public String login(){
        return "login success";
    }
}
此时,请求这个URI:http://localhost:8080/springboot-demo/user/login在5秒之内只能请求一次。
注意:标注在方法上的超时时间会覆盖掉类上的时间,因为如下一段代码:
Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "", Objects.nonNull(repeatSubmitByMethod)?repeatSubmitByMethod.seconds():repeatSubmitByCls.seconds(), TimeUnit.SECONDS);
这段代码的失效时间先取值repeatSubmitByMethod中配置的,如果为null,则取值repeatSubmitByCls配置的。
总结
至此,拦截器的内容就介绍完了,其实配置起来很简单,没什么重要的内容。

Spring Boot第七弹,别再问我拦截器如何配置了!!!的更多相关文章
- Spring Boot实现一个监听用户请求的拦截器
		
项目中需要监听用户具体的请求操作,便通过一个拦截器来监听,并继续相应的日志记录 项目构建与Spring Boot,Spring Boot实现一个拦截器很容易. Spring Boot的核心启动类继承W ...
 - Spring Boot入门系列(十)如何使用拦截器,一学就会!
		
前面介绍了Spring Boot 如何整合定时任务已经Spring Boot 如何创建异步任务,不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhon ...
 - Spring Boot 知识笔记(servlet、监听器、拦截器)
		
一.通过注解自定义servlet package net.Eleven.demo.servlet; import javax.servlet.ServletException; import java ...
 - spring boot / cloud (七) 使用@Retryable来进行重处理
		
spring boot / cloud (七) 使用@Retryable来进行重处理 前言 什么时候需要重处理? 在实际工作中,重处理是一个非常常见的场景,比如:发送消息失败,调用远程服务失败,争抢锁 ...
 - Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源
		
Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源 在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.今天就给大家介绍一个 Spri ...
 - Spring Boot (七): Mybatis极简配置
		
Spring Boot (七): Mybatis极简配置 1. 前言 ORM 框架的目的是简化编程中的数据库操作,经过这么多年的发展,基本上活到现在的就剩下两家了,一个是宣称可以不用写 SQL 的 H ...
 - Spring Boot第四弹,一文教你如何无感知切换日志框架?
		
持续原创输出,点击上方蓝字关注我吧 目录 前言 Spring Boot 版本 什么是日志门面? 如何做到无感知切换? 如何切换? 引入依赖 指定配置文件 日志如何配置? 总结 前言 首先要感谢一下读者 ...
 - Spring Boot 第六弹,拦截器如何配置,看这儿~
		
持续原创输出,点击上方蓝字关注我吧 目录 前言 Spring Boot 版本 什么是拦截器? 如何自定义一个拦截器? 如何使其在Spring Boot中生效? 举个栗子 思路 根据什么判断这个接口已经 ...
 - Spring Boot 揭秘与实战(五) 服务器篇 - Tomcat 代码配置
		
Spring Boot 内嵌的 Tomcat 服务器默认运行在 8080 端口.如果,我们需要修改Tomcat的端口,我们可以在 src/main/resources/application.prop ...
 
随机推荐
- Android开发之下载服务器上的一张图片到本地java代码实现HttpURLConnection
			
package com.david.HttpURLConnectionDemo; import java.io.FileOutputStream; import java.io.IOException ...
 - 使用Telnet服务测试端口时,提示没有Telnet服务
			
1.win7系统是默认不开启Telnet服务的,所以我们第一次使用时要手动开启Telnet服务 1)打开 控制面板 > 程序 > 程序功能 > 打开或关闭Windows功能,勾选上T ...
 - 太刺激了,面试官让我手写跳表,而我用两种实现方式吊打了TA!
			
前言 本文收录于专辑:http://dwz.win/HjK,点击解锁更多数据结构与算法的知识. 你好,我是彤哥. 上一节,我们一起学习了关于跳表的理论知识,相信通过上一节的学习,你一定可以给面试官完完 ...
 - Avtiviti工作流规范  BPM与BPMN
			
进过长时间的轮转,重拾Activiti,因为最近在智联上看到多家公司的需求上写的,都要熟悉工作流引擎,也就是activiti所以重拾 之前看的视屏是activiti5,我觉得版本有点低,所以打算看一下 ...
 - 20190917-02Linux网络配置 000 003
			
重启网络 最后reboot重启系统 继续ping 自己的window电脑的ip ctrl+C结束 继续在windows电脑上ping虚拟机上的linux系统
 - stackoverflow的ret2syscall利用
			
ret2syscall 系统调用 ret2syscall,即控制程序执行系统调用,获取shell.Linux将内核功能接口制作为系统调用(system call),可在程序中直接调用.程序中存在int ...
 - odoo10甘特图gantt view
			
odoo10中的gantt图示例 1.Gantt属性说明 甘特图视图的根元素是<gantt />,它没有子节点但可以采用以下属性: date_start (required) 提供每条记录 ...
 - Python爬虫学习第一记 (翻译小助手)
			
1 # Python爬虫学习第一记 8.24 (代码有点小,请放大看吧) 2 3 #实现有道翻译,模块一: $fanyi.py 4 5 import urllib.request 6 import u ...
 - Shader 001 - 函数造型能力
			
0x00 从函数出发 Shader 中的很多效果都是由函数计算得出的,如何更好地理解二者的关系呢.不妨先看看函数是什么?函数的定义可以简单地描述为:给定一个集合 A,对于其中的元素施加法则 f,则可以 ...
 - 反射之hasattr() getattr() setattr() 函数
			
Python的hasattr() getattr() setattr() 函数使用方法详解 hasattr(object, name)判断object中有没有一个name字符串对应的方法或属性,返回B ...