Springcloud学习笔记39--过滤器Filter和拦截器Interceptor详细使用
1.拦截器Filter定义
过滤器Filter基于Servlet实现,过滤器的主要应用场景是对字符编码、跨域等问题进行过滤。Servlet的工作原理是拦截配置好的客户端请求,然后对Request和Response进行处理。Filter过滤器随着web应用的启动而启动,只初始化一次。
Filter的使用比较简单,继承Filter 接口,实现对应的init、doFilter以及destroy方法即可。
1、init:在容器启动时调用初始化方法,只会初始化一次
2、doFilter:每次请求都会调用doFilter方法,通过FilterChain 调用后续的方法
3、destroy:当容器销毁时,执行destory方法,只会被调用一次。
下面是详细的代码编写方式:
@Component public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化拦截器"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //做一些处理 System.out.println("do some thing"); chain.doFilter(request,response); } @Override public void destroy() { System.out.println("销毁拦截器"); } }
有的人会遇到添加@Component, 本人亲测, 如果入口不添加@ServletComponentScan注解, 过滤器添加@Component也可以起作用, 不过你配置的urlPatterns将会失效, 默认会过滤/*, 这时不妨入口添加@ServletComponentScan注解, 这时你会发现会过滤你配置的路径, 而且无论你加不加@Component都不影响!
2.拦截器Interceptor定义
拦截器是SpringMVC中实现的一种基于Java反射(动态代理)机制的方法增强工具,拦截器的实现是继承HandlerInterceptor 接口,并实现接口的preHandle、postHandle和afterCompletion方法。
1、preHandle:请求方法前置拦截,该方法会在Controller处理之前进行调用,Spring中可以有多个Interceptor,这些拦截器会按照设定的Order顺序调用,当有一个拦截器在preHandle中返回false的时候,请求就会终止。
2、postHandle:preHandle返回结果为true时,在Controller方法执行之后,视图渲染之前被调用
3、afterCompletion:在preHandle返回ture,并且整个请求结束之后,执行该方法。
2.1 拦截器(Interceptor)执行顺序
(1)请求到达 DispatcherServlet
(2)DispatcherServlet 发送至 Interceptor ,执行 preHandle
(3)请求达到 Controller
(4)请求结束后,postHandle 执行
2.2 使用方法
HandlerInterceptorAdapter 这个适配器是由Spring MVC提供的(org.springframework.web.servlet.handler.HandlerInterceptorAdapter)继承此类,可以非常方便的实现自己的拦截器。
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { public HandlerInterceptorAdapter() { } public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //preHandle在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制等处理; return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { //在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView; } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { //在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面),可以根据ex是否为null判断是否发生了异常,进行日志记录; } public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
2.3 Spring Boot配置方式
2.3.1 自定义拦截器,需要继承HandlerInterceptorAdapter类
具体案例实现如下:
(1)拦截器AccessLogInterceptor
@Slf4j public class AccessLogInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("进入到拦截器AccessLogInterceptor中:preHandle() 方法"); String remoteAddr=getRequestIp(request); log.info("接收到来自[{}]请求",remoteAddr); return true; } private String getRequestIp(HttpServletRequest request) { String requestIp = request.getHeader("x-forwarded-for"); return requestIp; } }
(2)拦截器AuthorityInterceptor
@Slf4j public class AuthorityInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("进入到拦截器AuthorityInterceptor中:preHandle() 方法"); return true; } }
2.3.2 注册拦截器,需要实现WebMvcConfigurer接口
编写完拦截器之后,通过一个配置类设置拦截器,并且可以通过addPathPatterns和excludePathPatterns执行哪些请求需要被拦截,哪些不需要被拦截。
基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer
接口;
在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。SpringBoot 2.0 后,该类被标记为@Deprecated(弃用)。官方推荐直接实现WebMvcConfigurer。
需要重写addInterceptors方法,这里是对根目录"/"进行拦截,可以指定拦截url请求目录。
具体案例实现如下:
@Configuration public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { addV1Rule(registry); } private void addV1Rule(InterceptorRegistry registry) { //注册自己的拦截器并设置拦截的请求路径 registry.addInterceptor(new AccessLogInterceptor()).addPathPatterns("/**"); //拦截所有请求 registry.addInterceptor(new AuthorityInterceptor()).addPathPatterns("/student/getStudentName"); //拦截student相关请求 } }
2.3.3 案例测试结果分析
用户请求的url如下所示:
@RestController @RequestMapping("/student") @Slf4j public class StudentController { @Autowired private IStudentService studentService; @PostMapping("/getStudentName") public void getStudentName(){ log.info("studentName:lucky"); } }
postman请求的url: http://127.0.0.1:7010/student/getStudentName
控制台输出:
2022-01-24 13:47:15.599 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.interceptor.AccessLogInterceptor:18 | [] -进入到拦截器AccessLogInterceptor中:preHandle() 方法 2022-01-24 13:47:15.600 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.interceptor.AccessLogInterceptor:20 | [] -接收到来自[11112222]请求 2022-01-24 13:47:15.600 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.interceptor.AuthorityInterceptor:17 | [] -进入到拦截器AuthorityInterceptor中:preHandle() 方法 2022-01-24 13:47:15.605 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.controller.StudentController:41 | [] -studentName:lucky
3. 拦截器与过滤器的区别
相同点:
- 拦截器与过滤器都是体现了AOP的思想,对方法实现增强,都可以拦截请求方法。
- 拦截器和过滤器都可以通过Order注解设定执行顺序
不同点:
- 过滤器属于Servlet级别,拦截器属于Spring级别
Filter是在javax.servlet包中定义的,要依赖于网络容器,因此只能在web项目中使用。
Interceptor是SpringMVC中实现的,归根揭底拦截器是一个Spring组件,由Spring容器进行管理。
2、过滤器和拦截器的执行顺序不同:
下面通过一张图展示Filter和Interceprtor的执行顺序
首先当一个请求进入Servlet之前,过滤器的doFilter方法进行过滤,
进入Servlet容器之后,执行Controller方法之前,拦截器的preHandle方法进行拦截,
执行Controller方法之后,视图渲染之前,拦截器的postHandle方法进行拦截,
请求结束之后,执行拦截器的postHandle方法。
3、过滤器基于函数回调方式实现,拦截器基于Java反射机制实现
4. Springboot拦截器中获取request post请求中@RequestBody注解的Json请求参数
最近有一个需要从拦截器中获取post请求的参数的需求,这里记录一下处理过程中出现的问题。
于是,想到了使用流的方式,调用request.getInputStream()获取流,然后从流中读取参数;但是,经过拦截器后,参数经过@RequestBody注解赋值给controller中的方法的时候,却抛出了一个这样的异常:
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
Spring 中的 request.getInputStream()和 request.getReader()只能被读取一次,而@RequestBody注解底层也是通过流来请求数据,所以需要把拦截器中的数据流保存下来,让 controller 层可以读取的数据。
4.1 自定义一个 RequestWrapper 子类
package com.ttbank.flep.core.interceptor; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; /** * @Author lucky * @Date 2022/12/5 14:26 */ public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } }
4.2 拦截器层面
package com.ttbank.flep.core.interceptor; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @Author lucky * @Date 2022/1/24 9:58 */ @Slf4j public class AccessLogInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("进入到拦截器AccessLogInterceptor中:preHandle() 方法"); String remoteAddr=getRequestIp(request); log.info("接收到来自[{}]请求",remoteAddr); RequestWrapper requestWrapper = new RequestWrapper(request); String body = requestWrapper.getBody(); System.out.println(body); return true; } private String getRequestIp(HttpServletRequest request) { String requestIp = request.getHeader("x-forwarded-for"); if(StringUtils.isEmpty(requestIp)){ //获取请求封装的ip requestIp=request.getRemoteAddr(); } return requestIp; } }
4.3 过滤器Filter,用来把request传递下去
package com.ttbank.flep.core.filter; import com.ttbank.flep.core.interceptor.RequestWrapper; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @Author lucky * @Date 2022/12/5 14:30 */ @Component @WebFilter(urlPatterns = "/*") public class ChannelFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } if(requestWrapper == null) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
4.4 在启动类中注册拦截器
/** * @Author lucky * @Date 2021/11/25 16:44 */ @SpringBootApplication(scanBasePackages="com.ttbank") @EnableDiscoveryClient @EnableScheduling @MapperScan("com.ttbank.flep.core.mapper") @ServletComponentScan //注册过滤器注解 public class FileFlepApplication { public static void main(String[] args) { SpringApplication.run(FileFlepApplication.class,args); } }
此时,利用postman测试;
可见,controller中可以正常获取json内容了;
参考文献:
https://blog.csdn.net/qq_41973594/article/details/118164586 (拦截器--推荐)
https://blog.csdn.net/weixin_51110874/article/details/123319236(过滤器--推荐)
https://blog.csdn.net/zhangpower1993/article/details/89016503
https://blog.csdn.net/kuishao1314aa/article/details/109777304
https://www.mianshigee.com/note/detail/19996jdn/
https://www.jianshu.com/p/69c6fba08c92
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2019-01-24 office word使用技巧汇总
2019-01-24 翻译外文文献的两种方法