持續原創輸出,點擊上方藍字關注我吧

前言
上篇文章講了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
配置的。
總結
至此,攔截器的內容就介紹完了,其實配置起來很簡單,沒什么重要的內容。
