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
请求顺序.png
4 版本
4.1 maven依赖
Filter和Interceptor有spring-boot-starter-web依赖即可:
代码语言:txt复制<dependency>代码语言:txt复制 <groupId>org.springframework.boot</groupId>代码语言:txt复制 <artifactId>spring-boot-starter-web</artifactId>代码语言:txt复制 <version>2.2.9.RELEASE</version>代码语言:txt复制</dependency>AOP依赖的aspectJ需要额外的maven依赖:
代码语言:txt复制<dependency>代码语言:txt复制 <groupId>org.springframework.boot</groupId>代码语言:txt复制 <artifactId>spring-boot-starter-aop</artifactId>代码语言:txt复制 <version>2.2.9.RELEASE</version>代码语言:txt复制</dependency>4.2 测试Controller
代码语言:txt复制package com.pdd.module.lanjie.controller;代码语言:txt复制import org.springframework.web.bind.annotation.RequestMapping;代码语言:txt复制import org.springframework.web.bind.annotation.RestController;代码语言:txt复制import java.util.HashMap;代码语言:txt复制import java.util.Map;代码语言:txt复制@RestController代码语言:txt复制@RequestMapping("/my")代码语言:txt复制public class MyController {代码语言:txt复制 @RequestMapping("test")代码语言:txt复制 public Map<String,Object> test(String userName, String age) {代码语言:txt复制 String message = "[Controller Action]:userName=" userName ";age=" age;代码语言:txt复制 System.out.println(message);代码语言:txt复制 Map<String,Object> map = new HashMap<>();代码语言:txt复制 map.put("success",true);代码语言:txt复制 map.put("message",message);代码语言:txt复制 return map;代码语言:txt复制 }代码语言:txt复制}5 Filter代码实现
5.1 说明
(1)实现接口
实现接口:javax.servlet.Filter
(2)核心方法
代码语言:txt复制public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;5.2 定义
(1) 定义Filter
代码语言:txt复制package com.pdd.module.lanjie.filter;代码语言:txt复制import javax.servlet.*;代码语言:txt复制import java.io.IOException;代码语言:txt复制import java.util.Date;代码语言:txt复制/**代码语言:txt复制 * 计算执行时间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)配置
代码语言:txt复制package com.pdd.module.lanjie.filter.config;代码语言:txt复制import com.pdd.module.lanjie.filter.TimerFilter;代码语言:txt复制import org.springframework.boot.web.servlet.FilterRegistrationBean;代码语言:txt复制import org.springframework.context.annotation.Bean;代码语言:txt复制import org.springframework.context.annotation.Configuration;代码语言:txt复制import java.util.Arrays;代码语言:txt复制@Configuration代码语言:txt复制public class WebFilterConfig {代码语言:txt复制 @Bean代码语言:txt复制 public FilterRegistrationBean timerFilter() {代码语言:txt复制 FilterRegistrationBean registrationBean = new FilterRegistrationBean();代码语言:txt复制 // 设置:实现类代码语言:txt复制 registrationBean.setFilter(new TimerFilter());代码语言:txt复制 // 设置:UrlPatterns代码语言:txt复制 registrationBean.setUrlPatterns(Arrays.asList("/*"));代码语言:txt复制 // 设置:优先级代码语言:txt复制 registrationBean.setOrder(1);代码语言:txt复制 return registrationBean;代码语言:txt复制 }代码语言:txt复制}5.3 测试
(1)测试请求
代码语言:txt复制http://localhost:8080/my/test?userName=张三&age=23(2)输出结果
代码语言:txt复制[Filter-Time]:进入Filter代码语言:txt复制[Controller Action]:userName=张三;age=23代码语言:txt复制[Filter-Time]:结束Filter,共90毫秒5.4 配置顺序
代码语言:txt复制// 设置:优先级代码语言:txt复制registrationBean.setOrder(1);6 HandlerInterceptor代码实现
6.1 说明
(1)实现接口
实现接口:org.springframework.web.servlet.HandlerInterceptor
(2)核心方法
代码语言:txt复制// 调用Controller方法之前代码语言:txt复制boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;代码语言:txt复制// 调用Controller方法之后(不抛出异常)代码语言:txt复制void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception;代码语言:txt复制// 调用Controller方法之后(无论是否抛出异常)代码语言:txt复制void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception;6.2 定义
(1)定义Interceptor
代码语言:txt复制package com.pdd.module.lanjie.interceptor;代码语言:txt复制import com.alibaba.fastjson.JSONObject;代码语言:txt复制import org.springframework.stereotype.Component;代码语言:txt复制import org.springframework.web.method.HandlerMethod;代码语言:txt复制import org.springframework.web.servlet.HandlerInterceptor;代码语言:txt复制import org.springframework.web.servlet.ModelAndView;代码语言:txt复制import javax.servlet.http.HttpServletRequest;代码语言:txt复制import javax.servlet.http.HttpServletResponse;代码语言:txt复制import java.io.PrintWriter;代码语言:txt复制/**代码语言:txt复制 * 鉴权Interceptor
*/
@Component
public class AuthInterceptor implements HandlerInterceptor {
代码语言:txt复制 @Override代码语言:txt复制 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {代码语言:txt复制 System.out.println("[Interceptor-auth]:进入preHandle");代码语言:txt复制 if (handler instanceof HandlerMethod) {代码语言:txt复制 HandlerMethod handlerMethod = (HandlerMethod) handler;代码语言:txt复制 System.out.println("[Interceptor-auth]:访问信息=" handlerMethod.getShortLogMessage());代码语言:txt复制 // 获取head鉴权信息代码语言:txt复制 String sign = request.getHeader("sign");代码语言:txt复制 if (!"123456".equals(sign)) {代码语言:txt复制 // 鉴权不通过代码语言:txt复制 response.setCharacterEncoding("utf-8");代码语言:txt复制 response.setContentType("application/json; charset=utf-8");代码语言:txt复制 PrintWriter writer = response.getWriter();代码语言:txt复制 JSONObject jsonObject = new JSONObject();代码语言:txt复制 jsonObject.put("success", false);代码语言:txt复制 jsonObject.put("message", "鉴权失败");代码语言:txt复制 writer.write(jsonObject.toJSONString());代码语言:txt复制 writer.flush();代码语言:txt复制 writer.close();代码语言:txt复制 System.out.println("[Interceptor-auth]:----------鉴权不通过----------");代码语言:txt复制 System.out.println("[Interceptor-auth]:结束preHandle");代码语言:txt复制 return false;代码语言:txt复制 } else {代码语言:txt复制 // 鉴权通过代码语言:txt复制 System.out.println("[Interceptor-auth]:----------鉴权通过----------");代码语言:txt复制 System.out.println("[Interceptor-auth]:结束preHandle");代码语言:txt复制 return true;代码语言:txt复制 }代码语言:txt复制 }代码语言:txt复制 System.out.println("[Interceptor-auth]:结束preHandle");代码语言:txt复制 // 返回true为通过校验,返回false为不通过校验代码语言:txt复制 return true;代码语言:txt复制 }代码语言:txt复制 @Override代码语言:txt复制 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {代码语言:txt复制 System.out.println("[Interceptor-auth]:postHandle ModelAndView=" JSONObject.toJSONString(modelAndView));代码语言:txt复制 }代码语言:txt复制 @Override代码语言:txt复制 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {代码语言:txt复制 System.out.println("[Interceptor-auth]:afterCompletion Exception=" JSONObject.toJSONString(ex));代码语言:txt复制 }代码语言:txt复制}(2)配置
代码语言:txt复制package com.pdd.module.lanjie.interceptor.config;代码语言:txt复制import org.springframework.stereotype.Component;代码语言:txt复制import org.springframework.web.servlet.HandlerInterceptor;代码语言:txt复制import org.springframework.web.servlet.config.annotation.InterceptorRegistry;代码语言:txt复制import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;代码语言:txt复制import javax.annotation.Resource;代码语言:txt复制/**代码语言:txt复制 * WEB统一配置
*/
@Component
public class GlobalWebMvcConfigurer implements WebMvcConfigurer {
代码语言:txt复制 @Resource代码语言:txt复制 private HandlerInterceptor authInterceptor;代码语言:txt复制 @Override代码语言:txt复制 public void addInterceptors(InterceptorRegistry registry) {代码语言:txt复制 // 可按照顺序定义多个代码语言:txt复制 registry.addInterceptor(authInterceptor).addPathPatterns("/**");代码语言:txt复制 // 支持定义多个PathPattern和excludePathPatterns代码语言:txt复制 //registry.addInterceptor(xxxInterceptor).addPathPatterns("/xxx","/**").excludePathPatterns("/yyy","/zzz");代码语言:txt复制 }代码语言:txt复制}6.3 测试
6.3.1 正向测试
(1)测试请求
代码语言:txt复制http://localhost:8080/my/test?userName=张三&age=23代码语言:txt复制请求head:sign=123456(2)输出结果
- 控制台输出
[Interceptor-auth]:进入preHandle代码语言:txt复制[Interceptor-auth]:访问信息=com.pdd.module.lanjie.controller.MyController#test[2 args]代码语言:txt复制[Interceptor-auth]:----------鉴权通过----------代码语言:txt复制[Interceptor-auth]:结束preHandle代码语言:txt复制[Controller Action]:userName=张三;age=23代码语言:txt复制[Interceptor-auth]:postHandle ModelAndView=null代码语言:txt复制[Interceptor-auth]:afterCompletion Exception=null- 请求结果
{代码语言:txt复制 "success": true,代码语言:txt复制 "message": "[Controller Action]:userName=张三;age=23"代码语言:txt复制}6.3.2 逆向测试
(1)测试请求
代码语言:txt复制http://localhost:8080/my/test?userName=张三&age=23代码语言:txt复制请求head:无(2)输出结果
- 控制台输出
[Interceptor-auth]:进入preHandle代码语言:txt复制[Interceptor-auth]:访问信息=com.pdd.module.lanjie.controller.MyController#test[2 args]代码语言:txt复制[Interceptor-auth]:----------鉴权不通过----------代码语言:txt复制[Interceptor-auth]:结束preHandle- 请求结果
{代码语言:txt复制 "success": false,代码语言:txt复制 "message": "鉴权失败"代码语言:txt复制}6.4 配置顺序
代码语言:txt复制public void addInterceptors(InterceptorRegistry registry) {代码语言:txt复制 // 可按照顺序定义多个代码语言:txt复制 registry.addInterceptor(xxxInterceptor).addPathPatterns(xxx);代码语言:txt复制 registry.addInterceptor(yyyInterceptor).addPathPatterns(yyy);代码语言:txt复制}7 AOP代码实现
7.1 说明
相关注解
代码语言:txt复制org.aspectj.lang.annotation.Aspect代码语言:txt复制org.aspectj.lang.annotation.Pointcut代码语言:txt复制org.aspectj.lang.annotation.Before代码语言:txt复制org.aspectj.lang.annotation.After代码语言:txt复制org.aspectj.lang.annotation.Around7.2 定义
代码语言:txt复制import com.alibaba.fastjson.JSONObject;代码语言:txt复制import org.aspectj.lang.JoinPoint;代码语言:txt复制import org.aspectj.lang.ProceedingJoinPoint;代码语言:txt复制import org.aspectj.lang.annotation.*;代码语言:txt复制import org.springframework.core.annotation.Order;代码语言:txt复制import org.springframework.stereotype.Component;代码语言:txt复制@Component代码语言:txt复制@Order(1)代码语言:txt复制@Aspect代码语言:txt复制public class LogAop {代码语言:txt复制 @Pointcut("execution(public * com.pdd..controller..*(..))")代码语言:txt复制 public void log() {代码语言:txt复制 }代码语言:txt复制 @Before("log()")代码语言:txt复制 public void doBefore(JoinPoint joinPoint) {代码语言:txt复制 System.out.println("[AOP-log]:Before");代码语言:txt复制 }代码语言:txt复制 @After("log()")代码语言:txt复制 public void doAfter(JoinPoint joinPoint) {代码语言:txt复制 System.out.println("[AOP-log]:After");代码语言:txt复制 }代码语言:txt复制 @Around("log()")代码语言:txt复制 public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {代码语言:txt复制 System.out.println("[AOP-log]:Around-进入");代码语言:txt复制 // 请求参数代码语言:txt复制 System.out.println("[AOP-log]:Around-请求参数=" JSONObject.toJSONString(joinPoint.getArgs()));代码语言:txt复制 // 执行切面方法代码语言:txt复制 Object object = joinPoint.proceed();代码语言:txt复制 // 执行结果代码语言:txt复制 System.out.println("[AOP-log]:Around-执行结果=" JSONObject.toJSONString(object));代码语言:txt复制 System.out.println("[AOP-log]:Around-结束");代码语言:txt复制 return object;代码语言:txt复制 }代码语言:txt复制}7.3 测试
(1)测试请求
代码语言:txt复制http://localhost:8080/my/test?userName=张三&age=23(2)后台日志
代码语言:txt复制[AOP-log]:Around-进入代码语言:txt复制[AOP-log]:Around-请求参数=["张三","23"]代码语言:txt复制[AOP-log]:Before代码语言:txt复制[Controller Action]:userName=张三;age=23代码语言:txt复制[AOP-log]:After代码语言:txt复制[AOP-log]:Around-执行结果={"success":true,"message":"[Controller Action]:userName=张三;age=23"}代码语言:txt复制[AOP-log]:Around-结束(2)请求结果
代码语言:txt复制{"success":true,"message":"[Controller Action]:userName=张三;age=23"}7.4 配置顺序
通过spring注解org.springframework.core.annotation.Order,来定义顺序。
@Component代码语言:txt复制@Order(1)代码语言:txt复制@Aspect代码语言:txt复制public class LogAop {代码语言:txt复制 //......代码语言:txt复制}8 汇总测试
同时打开上述的Filter,Interceptor,AOP,一起来拦截请求。
(1)测试请求
代码语言:txt复制http://localhost:8080/my/test?userName=张三&age=23代码语言:txt复制请求head:sign=123456(2)输出结果
- 控制台输出
[Filter-Time]:进入Filter代码语言:txt复制[Interceptor-auth]:进入preHandle代码语言:txt复制[Interceptor-auth]:访问信息=com.pdd.module.lanjie.controller.MyController#test[2 args]代码语言:txt复制[Interceptor-auth]:----------鉴权通过----------代码语言:txt复制[Interceptor-auth]:结束preHandle代码语言:txt复制[AOP-log]:Around-进入代码语言:txt复制[AOP-log]:Around-请求参数=["张三","23"]代码语言:txt复制[AOP-log]:Before代码语言:txt复制[Controller Action]:userName=张三;age=23代码语言:txt复制[AOP-log]:After代码语言:txt复制[AOP-log]:Around-执行结果={"success":true,"message":"[Controller Action]:userName=张三;age=23"}代码语言:txt复制[AOP-log]:Around-结束代码语言:txt复制[Interceptor-auth]:postHandle ModelAndView=null代码语言:txt复制[Interceptor-auth]:afterCompletion Exception=null代码语言:txt复制[Filter-Time]:结束Filter,共326毫秒将输出内容,进行标注,和3 请求顺序,部分讲解的一致。
1615296824839.png
- 请求结果
{代码语言:txt复制 "success": true,代码语言:txt复制 "message": "[Controller Action]:userName=张三;age=23"代码语言:txt复制}


