前言
最近在Review代码时,发现有些同事在做请求信息增强处理时,有用到Filter过滤器,有用到Interceptor拦截器。虽然所做的需求都能用这两个达到效果了,但感觉同事对于这两个的区别还是有点分不清,甚至也不知道这两者在请求链路上处在什么位置,作用是什么。今天就整理了一下,把了解到的用图解出来,帮助大家理解这个Filter与Inteceptor的区别,以及后面如何使用这两者。
概念
一、过滤器
过滤器(Filter)属于Servlet的范畴, 通过实现javax.servlet.Filter接口来实现功能。 主要用于对用户请求进行预处理,是个典型的处理链。如果我们用的Spring框架,在javax.servlet.Filter的实现类中无法使到IOC容器中的Bean。因此它的作用范围要相对窄些。通常使用的场景:记录日志信息,解码,恶意请求的过滤,获取输入/输出流进行二次修改。
Filter具体流程可以分为以下几步:
1、filter的执行在Servlet之前,拦截请求过来的ServletRequest;
2、可根据需要自主地修改ServletRequest的请求信息,如Header等。
3、 在ServletResponse到达客户端之前,拦截ServletResponse
创建Filter必须实现javax.servlet.Filter接口,该接口定义了3个方法:
/**
* 初始化。被Web容器调用并将其放置service上。
* 实例化filter后会被servlet容器仅且调用一次该方法进行初始化工作。
* 该初始化工作必须完成后才能使用到过滤功能。若发生异常,无法使用该过滤器的过滤功能。
*/
public void init(FilterConfig filterConfig) throws ServletException;
/**
* 处理过滤器过滤工作。可对ServletRequest,ServletResponse做统一处理,可修改头部信息,多次读取响应流(需自行实现ServletRequest)等。
* 在客户端请求资源时会调用到doFilter这个方法。传入此方法的FilterChain会以链式调用传到下一个过滤器上。
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
/**
* 销毁。该方法会被Web容器调用一次进行资源回收。
*/
public void destroy();
其中doFilter就是实现过滤功能的核心方法,此方法上可以对 ServletRequest 预处理和 ServletResponse 后处理。
二、拦截器
拦截器(Inteceptor)属于Springframework的范畴, 通过实现org.springframework.web.servlet.HandlerInterceptor接口来实现功能。 这些实现类更贴近我们的业务,深入到Action的前后。通常使用的场景:校验用户权限,对控制器中的Action做增强,统一响应内容等。
创建Inteceptor必须实现 org.springframework.web.servlet.HandlerInterceptor接口,该接口定义了3个方法:
/**
*
* 该方法在具体的请求(Action)之前执行。拦截器也是一个链式执行处理的过程。
* DispatcherServlet通过HandlerMapping找到N个拦截器,并执行拦截器的各个方法。
* 每个拦截器都能决定是否中止执行执行链。
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* 该方法在具体的请求(Action)之后执行。但在DispatcherServlet进行渲染视图之前。可以对Controller调用之后的ModelAndView进行操作
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 该方法在DispatcherServlet进行渲染视图之后执行。主要用于资源清理
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
由接口的定义可以看出,每个方法都有一个默认的实现方法,也就是说,在实现HandlerInterceptor接口时,不必每个接口都需要自行实现,只需要有针对地对对应的方法进行实现就可以。但有一点需要注意的是,preHandle方法必须返回true,后面的操作才能进行下去。
我们再看看这两者在请求链上的所在位置
我们编写一个例子来看看过滤器与拦截器的调用位置与方式有啥不同。
1、先定义一个Controller:
/**
* @author kingman
* @since 2021-01-04 13:47
*/
@RequestMapping("home")
@Controller
public class HomeController {
@RequestMapping("sayhello")
@ResponseBody
public String sayHello()
{
System.out.println("HomeController sayHello method");
return "hello";
}
}
2、再定义一个Filter,前文提到,要做一个过滤器,就要实现javax.servlet.Filter这个接口:
/*** @author kingman* @since 2021-01-04 13:49*/@WebFilter(filterName = "filterDemo",urlPatterns = "/home/sayhello")@Componentpublic class FilterDemo implements javax.servlet.Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("FilterDemo init method"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Before FilterDemo doFilter method"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("After FilterDemo doFilter method"); } @Override public void destroy() { System.out.println("FilterDemo destroy method"); }}
3、再定义一个拦截器,同样地实现org.springframework.web.servlet.HandlerInterceptor接口,代码如下:
/**
* @author kingman
* @since 2021-01-04 13:51
*/
@Component
public class InteceptorDemo implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("InteceptorDemo preHandle method");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("InteceptorDemo postHandle method");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("InteceptorDemo afterCompletion method");
}
}
把拦截器注册到拦截器列表中
/**
* @author kingman
* @since 2021-01-04 13:54
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private InteceptorDemo inteceptorDemo;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(inteceptorDemo).addPathPatterns("/home/sayhello");
}
}
4、启动项目,运行http://localhost:8080/home/sayhello,查看日志:
我们整理一下整个过程的执行顺序
Filter.init->
Before Filter.doFilter->
HandlerInterceptor.preHandle->
HandlerInterceptor.postHandle->
HandlerInterceptor.afterCompletion->
After Filter.doFilter
从这里的输出看出了从客户端请求到过滤器到Servlet到拦截器到控制器再往上返回响应内容的过滤,也印证了上述请求链的那张图的正确性。
这里只是一个例子,我们能在Filter能做什么,Interceptor能做什么,在什么时候做,这样已经一目了然了,至于它们的工作范围,就交由使用方去决定了。
扩展:
上述只是一个比较粗粒度的例子,再深挖可以去了解到Servlet在Spring中是如何应用处理请求的。以下也给了一个整理后的图,以后有时间也出一篇关于Servlet的请求链分析。