请求拦截之filter、interceptor、aop

1 场景
web程序中,对用户的请求,经常会对请求进行拦截处理,常用的处理方式如下:

  • Filter
  • Interceptor
  • AOP
本文基于SpringBoot的web程序,进行这三种拦截方式的说明。
2 区别
三种拦截方式的区别如下:
依赖 Servlet容器 Spring Web Spring
基于实现 回调机制 反射机制(AOP思想) 动态代理
类别 Filter Interceptor AOP
实现方式 实现接口Filter 实现接口HandlerInterceptor 注解@Aspect
作用范围 所有URL请求(可过滤) 所有Controller的action
包括自己定义的和其他组件定义的
spring的bean(可过滤)
可操作数据 原始Http请求信息:
ServletRequest request,
ServletResponse response
(1)Http请求信息:
HttpServletRequest request,
HttpServletResponse response,

(2)springMvc执行的方法信息:
HandlerMethod handlerMethod

(3)返回结果(执行Action方法后,不报错):
ModelAndView modelAndView

(4)异常信息(执行Action方法后):
Exception ex
请求参数
返回结果
异常信息
不可操作数据 执行方法相关信息 ResponseBody的返回结果 http请求信息
相关方法 doFilter preHandle
postHandle
afterCompletion@
@Aspect
@Pointcut
@Before
@After
@Around
用途 字符编码,
鉴权操作,
防重复提交
记录执行时间,
脱敏信息、
过滤敏感词、
多租户切换
......
字符编码
鉴权操作
防重复提交
异常记录
......
日志记录
异常记录
数据源切换
请求埋点
......
3 请求顺序
基于SpringBoot的web程序,Filter、Interceptor、Aop的请求顺序如下:
Filter->Interceptor->AOP->Controller
请求拦截之filter、interceptor、aop
文章图片
请求顺序.png 4 版本
4.1 maven依赖 Filter和Interceptor有spring-boot-starter-web依赖即可:
org.springframework.boot spring-boot-starter-web 2.2.9.RELEASE

AOP依赖的aspectJ需要额外的maven依赖:
org.springframework.boot spring-boot-starter-aop 2.2.9.RELEASE

4.2 测试Controller
package com.pdd.module.lanjie.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/my") public class MyController {@RequestMapping("test") public Map test(String userName, String age) { String message = "[Controller Action]:userName=" + userName + ";age=" + age; System.out.println(message); Map map = new HashMap<>(); map.put("success",true); map.put("message",message); return map; } }

5 Filter代码实现
5.1 说明 【请求拦截之filter、interceptor、aop】(1)实现接口
实现接口:javax.servlet.Filter
(2)核心方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

5.2 定义 (1) 定义Filter
package com.pdd.module.lanjie.filter; import javax.servlet.*; import java.io.IOException; import java.util.Date; /** * 计算执行时间Filter */ public class TimerFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long begin = new Date().getTime(); System.out.println("[Filter-Time]:进入Filter"); // 执行servlet方法(如拦截请求,不执行Servlet,可不执行此方法) chain.doFilter(request, response); long end = new Date().getTime(); System.out.println("[Filter-Time]:结束Filter,共" + (end - begin) + "毫秒"); } }

(2)配置
package com.pdd.module.lanjie.filter.config; import com.pdd.module.lanjie.filter.TimerFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Arrays; @Configuration public class WebFilterConfig { @Bean public FilterRegistrationBean timerFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); // 设置:实现类 registrationBean.setFilter(new TimerFilter()); // 设置:UrlPatterns registrationBean.setUrlPatterns(Arrays.asList("/*")); // 设置:优先级 registrationBean.setOrder(1); return registrationBean; } }

5.3 测试 (1)测试请求
http://localhost:8080/my/test?userName=张三&age=23

(2)输出结果
[Filter-Time]:进入Filter [Controller Action]:userName=张三;age=23 [Filter-Time]:结束Filter,共90毫秒

5.4 配置顺序
// 设置:优先级 registrationBean.setOrder(1);

6 HandlerInterceptor代码实现
6.1 说明 (1)实现接口
实现接口:org.springframework.web.servlet.HandlerInterceptor
(2)核心方法
// 调用Controller方法之前 boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; // 调用Controller方法之后(不抛出异常) void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception; // 调用Controller方法之后(无论是否抛出异常) void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception;

6.2 定义 (1)定义Interceptor
package com.pdd.module.lanjie.interceptor; import com.alibaba.fastjson.JSONObject; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * 鉴权Interceptor */ @Component public class AuthInterceptor implements HandlerInterceptor {@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("[Interceptor-auth]:进入preHandle"); if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; System.out.println("[Interceptor-auth]:访问信息=" + handlerMethod.getShortLogMessage()); // 获取head鉴权信息 String sign = request.getHeader("sign"); if (!"123456".equals(sign)) { // 鉴权不通过 response.setCharacterEncoding("utf-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter writer = response.getWriter(); JSONObject jsonObject = new JSONObject(); jsonObject.put("success", false); jsonObject.put("message", "鉴权失败"); writer.write(jsonObject.toJSONString()); writer.flush(); writer.close(); System.out.println("[Interceptor-auth]:----------鉴权不通过----------"); System.out.println("[Interceptor-auth]:结束preHandle"); return false; } else { // 鉴权通过 System.out.println("[Interceptor-auth]:----------鉴权通过----------"); System.out.println("[Interceptor-auth]:结束preHandle"); return true; } } System.out.println("[Interceptor-auth]:结束preHandle"); // 返回true为通过校验,返回false为不通过校验 return true; }@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("[Interceptor-auth]:postHandle ModelAndView=" + JSONObject.toJSONString(modelAndView)); }@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("[Interceptor-auth]:afterCompletion Exception=" + JSONObject.toJSONString(ex)); } }

(2)配置
package com.pdd.module.lanjie.interceptor.config; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * WEB统一配置 */ @Component public class GlobalWebMvcConfigurer implements WebMvcConfigurer {@Resource private HandlerInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 可按照顺序定义多个 registry.addInterceptor(authInterceptor).addPathPatterns("/**"); // 支持定义多个PathPattern和excludePathPatterns //registry.addInterceptor(xxxInterceptor).addPathPatterns("/xxx","/**").excludePathPatterns("/yyy","/zzz"); } }

6.3 测试 6.3.1 正向测试 (1)测试请求
http://localhost:8080/my/test?userName=张三&age=23请求head:sign=123456

(2)输出结果
  • 控制台输出
[Interceptor-auth]:进入preHandle [Interceptor-auth]:访问信息=com.pdd.module.lanjie.controller.MyController#test[2 args] [Interceptor-auth]:----------鉴权通过---------- [Interceptor-auth]:结束preHandle [Controller Action]:userName=张三;age=23 [Interceptor-auth]:postHandle ModelAndView=null [Interceptor-auth]:afterCompletion Exception=null

  • 请求结果
{ "success": true, "message": "[Controller Action]:userName=张三;age=23" }

6.3.2 逆向测试 (1)测试请求
http://localhost:8080/my/test?userName=张三&age=23请求head:无

(2)输出结果
  • 控制台输出
[Interceptor-auth]:进入preHandle [Interceptor-auth]:访问信息=com.pdd.module.lanjie.controller.MyController#test[2 args] [Interceptor-auth]:----------鉴权不通过---------- [Interceptor-auth]:结束preHandle

  • 请求结果
{ "success": false, "message": "鉴权失败" }

6.4 配置顺序
public void addInterceptors(InterceptorRegistry registry) { // 可按照顺序定义多个 registry.addInterceptor(xxxInterceptor).addPathPatterns(xxx); registry.addInterceptor(yyyInterceptor).addPathPatterns(yyy); }

7 AOP代码实现
7.1 说明 相关注解
org.aspectj.lang.annotation.Aspect org.aspectj.lang.annotation.Pointcut org.aspectj.lang.annotation.Before org.aspectj.lang.annotation.After org.aspectj.lang.annotation.Around

7.2 定义
import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Order(1) @Aspect public class LogAop {@Pointcut("execution(public * com.pdd..controller..*(..))") public void log() {}@Before("log()") public void doBefore(JoinPoint joinPoint) { System.out.println("[AOP-log]:Before"); }@After("log()") public void doAfter(JoinPoint joinPoint) { System.out.println("[AOP-log]:After"); }@Around("log()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("[AOP-log]:Around-进入"); // 请求参数 System.out.println("[AOP-log]:Around-请求参数="+ JSONObject.toJSONString(joinPoint.getArgs())); // 执行切面方法 Object object = joinPoint.proceed(); // 执行结果 System.out.println("[AOP-log]:Around-执行结果="+ JSONObject.toJSONString(object)); System.out.println("[AOP-log]:Around-结束"); return object; }}

7.3 测试 (1)测试请求
http://localhost:8080/my/test?userName=张三&age=23

(2)后台日志
[AOP-log]:Around-进入 [AOP-log]:Around-请求参数=["张三","23"] [AOP-log]:Before [Controller Action]:userName=张三;age=23 [AOP-log]:After [AOP-log]:Around-执行结果={"success":true,"message":"[Controller Action]:userName=张三;age=23"} [AOP-log]:Around-结束

(2)请求结果
{"success":true,"message":"[Controller Action]:userName=张三;age=23"}

7.4 配置顺序 通过spring注解org.springframework.core.annotation.Order,来定义顺序。
@Component @Order(1) @Aspect public class LogAop { //...... }

8 汇总测试
同时打开上述的Filter,Interceptor,AOP,一起来拦截请求。
(1)测试请求
http://localhost:8080/my/test?userName=张三&age=23请求head:sign=123456

(2)输出结果
  • 控制台输出
[Filter-Time]:进入Filter [Interceptor-auth]:进入preHandle [Interceptor-auth]:访问信息=com.pdd.module.lanjie.controller.MyController#test[2 args] [Interceptor-auth]:----------鉴权通过---------- [Interceptor-auth]:结束preHandle [AOP-log]:Around-进入 [AOP-log]:Around-请求参数=["张三","23"] [AOP-log]:Before [Controller Action]:userName=张三;age=23 [AOP-log]:After [AOP-log]:Around-执行结果={"success":true,"message":"[Controller Action]:userName=张三;age=23"} [AOP-log]:Around-结束 [Interceptor-auth]:postHandle ModelAndView=null [Interceptor-auth]:afterCompletion Exception=null [Filter-Time]:结束Filter,共326毫秒

将输出内容,进行标注,和3 请求顺序,部分讲解的一致。
请求拦截之filter、interceptor、aop
文章图片
1615296824839.png
  • 请求结果
{ "success": true, "message": "[Controller Action]:userName=张三;age=23" }

    推荐阅读