之前实际开发项目的时候,虽然有用过滤器和拦截器,但是理解上还是有点懵懵懂懂的,没有彻底明白,这篇文章就来仔细剖析下这二者的区别与联系。
过滤器Filter,是在Servlet规范中定义的,是Servlet容器支持的,该接口定义在 javax.servlet
包下,主要是在客户端请求(HttpServletRequest)进行预处理,以及对服务器响应(HttpServletResponse)进行后处理。接口代码如下:
package javax.servlet; import java.io.IOException; public interface Filter { void init(FilterConfig var1) throws ServletException; void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; void destroy(); } 复制代码
对上面三个接口方法进行分析:
FilterChain.doFilter
可以将请求继续传递下去,如果想拦截这个请求,可以不调用FilterChain.doFilter,那么这个请求就直接返回了, 所以Filter是一种责任链设计模式 ,在 spring security
就大量使用了过滤器,有一条过滤器链。 在springboot自定义Filter类如下:
@Component public class MyFilter implements Filter { private Logger logger = LoggerFactory.getLogger(MyFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { logger.info("filter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { logger.info("doFilter"); //对request,response进行预处理 //TODO 进行业务逻辑 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { logger.info("filter destroy"); } } 复制代码
在springboot中提供了 FilterRegistrationBean
方式,此类提供setOrder方法,可以为多个filter设置排序值。代码如下:
@Configuration public class FilterConfig { /** * 配置一个Filter注册器 * * @return */ @Bean public FilterRegistrationBean filterRegistrationBean1() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter1()); registrationBean.setName("filter1"); //设置顺序 registrationBean.setOrder(10); return registrationBean; } @Bean public FilterRegistrationBean filterRegistrationBean2() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter2()); registrationBean.setName("filter2"); //设置顺序 registrationBean.setOrder(3); return registrationBean; } @Bean public Filter filter1() { return new MyFilter(); } @Bean public Filter filter2() { return new MyFilter2(); } } 复制代码
拦截器是Spring提出的概念,它的作用于过滤器类似,可以拦截用户请求并进行相应的处理,它可以进行更加精细的控制。
在SpringMVC中,DispatcherServlet捕获每个请求,在到达对应的Controller之前,请求可以被拦截器处理,在拦截器中进行前置处理后,请求最终才到达Controller。
拦截器的接口是 org.springframework.web.servlet.HandlerInterceptor
接口,接口代码如下:
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } } 复制代码
接口方法解读:
preHandle
方法返回 true。具体来说, postHandler
方法会在DispatcherServlet进行视图返回渲染前被调用,也就是说我们可以在这个方法中对 Controller 处理之后的 ModelAndView
对象进行操作 preHandle
方法的返回值为 true才行。该方法一般用于资源清理工作 public class MyInterceptor implements HandlerInterceptor { private Logger logger = LoggerFactory.getLogger(MyInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("preHandle...."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { logger.info("postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.info("afterCompletion..."); } } 复制代码
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(handlerInterceptor()) //配置拦截规则 .addPathPatterns("/**"); } @Bean public HandlerInterceptor handlerInterceptor() { return new MyInterceptor(); } } 复制代码
在springMVC中我们可以实现多个拦截器,并依次将他们注册进去,如下:
public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(handlerInterceptor()) .addPathPatterns("/**"); registry.addInterceptor(handlerInterceptor2()) .addPathPatterns("/**"); } 复制代码
拦截器的顺序也跟他们注册时的顺序有关,至少 preHandle
方法是这样,下图表示了两个拦截器协同工作时的执行顺序:
上图出自慕课网
后台打印日志也输出了相同的执行顺序:
io-9999-exec-2] c.p.filter.interceptor.MyInterceptor : preHandle.... 2018-09-13 12:13:31.292 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : preHandle2.... 2018-09-13 12:13:31.388 INFO 9736 --- [nio-9999-exec-2] c.p.filter.controller.HelloController : username:pjmike,password:123456 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : postHandle2... 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : postHandle... 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : afterCompletion2... 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : afterCompletion... 复制代码
从上面对拦截器与过滤器的描述来看,它俩是非常相似的,都能对客户端发来的请求进行处理,它们的区别如下:
preHandle
简单总结一下,拦截器相比过滤器有更细粒度的控制,依赖于Spring容器,可以在请求之前或之后启动,过滤器主要依赖于servlet,过滤器能做的,拦截器基本上都能做。