业务场景:
项目中需要使用请求头传输一个密文字符串,后端服务获取密文字符串后,进行解密验证,然后执行响应的业务,这里有好几个接口都用需要使用这个密文字符串,如果我们在每个接口中进行校验处理,就显得太笨拙了,那有没有统一的处理方法呢?我想到了使用拦截器 HandlerInterceptor 处理,以下是使用分享。
什么是拦截器?
拦截器,顾名思义,就是拦截,对用户请求进行拦截过滤处理。
拦截器的生效时机?
- 请求进入 Controller 之前,通过拦截器请求执行相关逻辑(符合我们上面的场景)。
- Controller 执行之后,只是Controller执行完毕,还没到视图渲染,通过拦截器执行相关逻辑。
- Controller 执行完毕,请求全部结束,通过拦截器执行相关逻辑。
HandlerInterceptor 源码:
package org.springframework.web.servlet;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}
自定义拦截器:
HandlerInterceptor 接口只给我们定义了方法,具体的业务逻辑需要我们自己去实现,自定义拦截器需要实现HandlerInterceptor ,代码如下:
package com.study.web.intercept;import cn.hutool.core.util.StrUtil;
import com.study.utils.DesECBUtil;
import com.study.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
@Component
public class MyIntercept implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//header 中获取密文String code = request.getHeader("code");if (StringUtils.isBlank(code)) {//为空 不通过throw new BusinessException("code 为空,请核实后重试");}//校验String userCode;try {userCode = DesECBUtil.decryptDES(new String(Base64.decodeBase64(code)), ztmcSecretKey);} catch (Exception e) {log.error("code 解密失败,失败原因:", e);throw new BusinessException("code 解密失败,请联系管理员处理");}//这个code 有时间戳String[] userCodeArr = userCode.split(StrUtil.UNDERLINE);//解密出来的是 usercode_时间戳String userNo = userCodeArr[0];//获取时间戳long timestamp = Long.parseLong(userCodeArr[1]);//获取当前时间戳long currentTimestamp = System.currentTimeMillis() / 1000;//当前时间戳-解密时间戳>30分钟 即过期log.info("当前时间戳秒:{},解密时间戳秒:{}",currentTimestamp,timestamp);if (currentTimestamp - timestamp > 1800) {//链接已失效throw new BusinessException("链接已失效,请重新打开链接");}//设置coderequest.setAttribute("code", userNo);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}
我们这里是需要校验header 中的密文字符串是否合法,故要前置校验,所以使用了preHandle 方法。
配置拦截器
我们将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中,并指定需要拦截的接口路径。
package com.study.web.config;import com.study.web.intercept.PerformanceReviewIntercept;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {@Resourceprivate MyIntercept MyIntercept ;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(performanceReviewIntercept)//添加需要拦截的接口路径.addPathPatterns("intercept/intercept-demo/*");}}
至此,这已经是一个完成可用的拦截器了,可以帮我们统一进行 code 解密验证,就无需去每个 Controller 去处理了。
拦截器(Interceptor)和过滤器(Filter)的区别?
- 实现原理不同:拦截器是基于Java反射机制实现,而过滤器是基于函数回调的。
- 使用范围不同:拦截器是 Spring 的一个组件,由 Spring 容器管理,无需依赖 Tomcat 等容器,可以单独使用,而过滤器实现了 Servlet 接口,导致过滤器的使用要依赖于Tomcat等容器,所以他只能在web程序中使用。
- 触发时机不同:拦截器是在请求进入servlet后,在进入Controller之前、之后、完毕进行处理的,而过滤器是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
- 拦截的请求范围不同:拦截器只会对Controller中请求或访问static目录下的资源请求起作用,而过滤器几乎可以对所有进入容器的请求起作用。
- 拦截器可以获取IOC容器中的各个bean,在拦截器里注入一个bean,可以调用各种业务逻辑,而过滤器就则不可以。
拦截器(Interceptor)和过滤器(Filter)的相似之处?
Spring的拦截器与Servlet的Filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。
总结:过滤器只能在 Servlet 前后起作用,而拦截器能够到方法前后、异常抛出等,明显拦截器更具备深度,并且拦截器是 Spring 的一个组件,因此拦截器的使用具有更大的使用空间,在Spring 的程序中建议优先使用拦截器,而非过滤器。
如有不正确的地方请各位指出纠正。