JAVA 拦截器
简介
拦截器和过滤器均可以拦截http请求,过滤器偏向于基础设施工作,拦截器偏向于业务,拦截器允许在执行Controller之前做验证预处理,在Controller执行之后对返回对象做加工处理。可以用于:权限检查、日志记录、性能监控等。Spring框架为拦截器的实现提供了强大的支持,使得我们可以在Spring MVC应用中轻松地使用拦截器。
实现原理
拦截器实现主要基于两个原理:AOP(动态代理)和责任链模式
AOP:是面向切面的编程的方式,可以在不改变源代码的基础上,进行业务扩展。
责任链模式:类似过滤器链,可以有多个拦截处理,一个处理后放行,下一个拦截才能处理。
作用
- 日志记录
- 流量管理
- 接口鉴权
主要方法
拦截器的执行先后顺序主要由WebMvcConfigurer子类的注册顺序实现的。
- WebMvcConfigurer方法:
- addResourceHandlers:指定静态资源的位置并如何访问;
- addInterceptors:添加拦截器,并指定需要拦截的路径;多个拦截器的执行顺序由添加顺序决定的。
- HandlerInterceptor方法:
- preHandle:每次http调用Controller执行执行,返回为true执行下一步请求,false请求结束,按照注册顺序执行。
- postHandle:每次http调用Controller之后执行,对返回结果的统一渲染,按照注册顺序反执行。
- afterCompletion:在整个请求处理完成后执行的逻辑,包括视图渲染之后,按照注册顺序反执行;
- 执行顺序(假如先后注册了interceptor01和interceptor02)
- interceptor01.preHandle > interceptor02.preHandle > controller的逻辑业务执行 > interceptor02.postHandle > interceptor01.postHandle > interceptor02.afterCompletion > interceptor01.afterCompletion
拦截器和过滤器的区别
- 实现原理不同:
- 过滤器:基于filterChain.doFilter函数回调实现,依赖servlet;对所有请求起作用;
- 拦截器:基于SpringAOP、动态代理、责任链和放蛇实现,依赖SpringMVC;只能对action请求起作用,可以访问action的上下文;
- 执行顺序不同:(Tomcat->Filter->Servlet->Interceptor->Controller)过滤器先执行,到servlet,Dispatcher servlet调用拦截器,分发给Controller,在Controller处理之前和处理之后执行;
- 使用方式不同:过滤器需要依赖servlet提供的接口,拦截器需要依赖SpringMVC的HandleInterceptor接口,执行循序由WebMvcConfigurer的配置循序执行决定的。
- 处理范围不同:
- 过滤器:只能对request和response进行操作;
- 拦截器:request,response,handler,modelAndView,exception执行操作
- 用途不同:
- 过滤器:处理必要的基础设施工作,编码处理,试图响应,请求参数处理和URL重定向;
- 拦截器:主要和业务相关,例如身份认真和授权,接口的性能监控,跨域处理日志等;
案例(多个拦截器+校验token)
这是一个学习过程中的应用测试,仅供参考:
创建过滤器MyInterceptor01:
@Slf4j
@Component//注意当前类必须受Spring容器控制
@RequiredArgsConstructor
public class MyInterceptor01 implements HandlerInterceptor {
private final ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse servletResponse, Object handler) throws Exception {
// 在请求处理之前执行的逻辑
HandlerMethod handlerMethod = (HandlerMethod) handler;
String method = handlerMethod.getClass().getName() + handlerMethod.getMethod().getName();
log.info("preHandle01: {}", method);
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)) {
servletResponse.setContentType("text/plain;charset=UTF-8"); // 设置内容类型和编码
try {
BaseResponse<String> baseResponse = ResultUtils.error("用户未登录,请先登录.");
String responseStr = objectMapper.writeValueAsString(baseResponse);
ServletOutputStream outputStream = servletResponse.getOutputStream();
outputStream.write(responseStr.getBytes(StandardCharsets.UTF_8)); // 写入字节数组
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return false;
}
return true; // 返回true表示继续执行后续操作,返回false表示中断请求处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后执行的逻辑,视图渲染之前
log.info("postHandle01");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求处理完成后执行的逻辑,包括视图渲染之后
log.info("afterCompletion01");
}
}
创建过滤器MyInterceptor02:
@Slf4j
@Component //注意当前类必须受Spring容器控制
public class MyInterceptor02 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前执行的逻辑
log.info("preHandle02");
return true; // 返回true表示继续执行后续操作,返回false表示中断请求处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后执行的逻辑,视图渲染之前
log.info("postHandle02");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求处理完成后执行的逻辑,包括视图渲染之后
log.info("afterCompletion02");
}
}
注册过滤器:先添加MyInterceptor021,再添加MyInterceptor02,执行顺序由注册顺序决定;
@Configuration
public class WebInterceptorConfiguration implements WebMvcConfigurer {
@Autowired
private MyInterceptor01 myInterceptor01;
@Autowired
private MyInterceptor02 myInterceptor02;
/**
* 指定静态资源的位置,和如何访问这些资源
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 将所有 /static/** 访问都映射到 classpath:/static/ 目录下
// 静态资源请求的映射,如静态请求为/static/**,则请求会在程序的类路径(通常是src/main/static)下的/static/目录中查找这些资源
// registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
/**
* addPathPatterns:需要过滤的路劲
* excludePathPatterns:需要拦截的路劲
* 拦截器的执行循序和配置顺序有关,先进后出
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor01)
.addPathPatterns("/api/**") // 拦截的路径
.excludePathPatterns("/public/**"); // 排除的路径
registry.addInterceptor(myInterceptor02)
.addPathPatterns("/api/**") // 拦截的路径
.excludePathPatterns("/public/**"); // 排除的路径
}
}
测试结果:不加token的时候请求被拦截提示未登录
测试结果:增加token,查看HandlerInterceptor的几个方法的执行顺序
参考地址:
https://www.cnblogs.com/blogtech/p/18186170
https://blog.csdn.net/qq_34207422/article/details/136619868
https://www.jb51.net/article/219535.htm