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
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 说明
(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 请求顺序
,部分讲解的一致。
- 请求结果
{
"success": true,
"message": "[Controller Action]:userName=张三;age=23"
}