本文收录在个人博客:www.chengxy-nds.top,同进步

周末有个小伙伴加我微信,向我请教了一个问题:老哥,过滤器 (**Filter**) 和 拦截器 (**Interceptor**) 有啥区别啊? 听到题目我的第一感觉就是:简单

毕竟这两种工具开发中用到的频率都相当高,应用起来也是比较简单的,可当我准备回复他的时候,竟然不知道从哪说起,支支吾吾了半天,场面炒鸡尴尬有木有,工作这么久一个基础问题答成这样,丢了大人了。
过滤器 和 拦截器 6个区别 - 图1

平时觉得简单的知识点,但通常都不会太关注细节,一旦被别人问起来,反倒说不出个所以然来。

归根结底,还是对这些知识了解的不够,一直停留在会用的阶段,以至于现在一看就会一说就废!这是典型基础不扎实的表现,哎 ·~,其实我也就是个虚胖!

知耻而后勇,下边结合实践,更直观的来感受一下两者到底有什么不同?

准备环境

我们在项目中同时配置 拦截器过滤器

1、过滤器 (Filter)

过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。

  • init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter
  • destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
    @Component
    public class MyFilter implements Filter {
    } ``` @Override public void init(FilterConfig filterConfig) throws ServletException {

    System.out.println(“Filter 前置”); }

@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  1. System.out.println("Filter 处理中");
  2. filterChain.doFilter(servletRequest, servletResponse);

}

@Override public void destroy() {

  1. System.out.println("Filter 后置");

}

  1. <a name="343e52ce"></a>
  2. #### 2、拦截器 (Interceptor)
  3. 拦截器它是链式调用,一个应用中可以同时存在多个拦截器`Interceptor`, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。
  4. 首先编写一个简单的拦截器处理类,请求的拦截是通过`HandlerInterceptor` 来实现,看到`HandlerInterceptor` 接口中也定义了三个方法。
  5. - `preHandle()` :这个方法将在请求处理之前进行调用。**注意**:如果该方法的返回值为`false` ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
  6. - `postHandle()`:只有在 `preHandle()` 方法返回值为`true` 时才会执行。会在 Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 **有意思的是**:`postHandle()` 方法被调用的顺序跟 `preHandle()` 是相反的,先声明的拦截器 `preHandle()` 方法先执行,而`postHandle()`方法反而会后执行。
  7. - `afterCompletion()`:只有在 `preHandle()` 方法返回值为`true` 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。<br />[@Component ](/Component ) <br />public class MyInterceptor implements HandlerInterceptor { <br />}

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

  1. System.out.println("Interceptor 前置");
  2. return true;

}

@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

  1. System.out.println("Interceptor 处理中");

}

@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  1. System.out.println("Interceptor 后置");

}

  1. 将自定义好的拦截器处理类进行注册,并通过`addPathPatterns``excludePathPatterns`等属性设置需要拦截或需要排除的 `URL`

@Configuration public class MyMvcConfig implements WebMvcConfigurer {

  1. @Override
  2. public void addInterceptors(InterceptorRegistry registry) {
  3. registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
  4. registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
  5. }

}

  1. <a name="87ba2c88"></a>
  2. ### 我们不一样
  3. 过滤器 和 拦截器 均体现了`AOP`的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。
  4. <a name="13a2a09d"></a>
  5. #### 1、实现原理不同
  6. 过滤器和拦截器 底层实现方式大不相同,`过滤器` 是基于函数回调的,`拦截器` 则是基于 Java 的反射机制(动态代理)实现的。
  7. 这里重点说下过滤器!
  8. 在我们自定义的过滤器中都会实现一个 `doFilter()`方法,这个方法有一个`FilterChain` 参数,而实际上它是一个回调接口。`ApplicationFilterChain`是它的实现类, 这个实现类内部也有一个 `doFilter()` 方法就是回调方法。

public interface FilterChain { void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; }

  1. ![](https://img-blog.csdnimg.cn/2020060311274589.png?#pic_center#id=BYvzq&originHeight=561&originWidth=639&originalType=binary&ratio=1&status=done&style=none)
  2. `ApplicationFilterChain`里面能拿到我们自定义的`xxxFilter`类,在其内部回调方法`doFilter()`里调用各个自定义`xxxFilter`过滤器,并执行 `doFilter()` 方法。

public final class ApplicationFilterChain implements FilterChain { @Override public void doFilter(ServletRequest request, ServletResponse response) { … internalDoFilter(request,response); }

  1. private void internalDoFilter(ServletRequest request, ServletResponse response){
  2. if (pos < n) {
  3. ApplicationFilterConfig filterConfig = filters[pos++];
  4. Filter filter = filterConfig.getFilter();
  5. ...
  6. filter.doFilter(request, response, this);
  7. }
  8. }

}

  1. 而每个`xxxFilter` 会先执行自身的 `doFilter()` 过滤逻辑,最后在执行结束前会执行`filterChain.doFilter(servletRequest, servletResponse)`,也就是回调`ApplicationFilterChain``doFilter()` 方法,以此循环执行实现函数回调。

@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  1. filterChain.doFilter(servletRequest, servletResponse);
  2. }
  1. <a name="cb5df5f4"></a>
  2. #### 2、使用范围不同
  3. 我们看到过滤器 实现的是 `javax.servlet.Filter` 接口,而这个接口是在`Servlet`规范中定义的,也就是说过滤器`Filter` 的使用要依赖于`Tomcat`等容器,导致它只能在`web`程序中使用。<br />![](https://img-blog.csdnimg.cn/20200602141320121.png?#id=rMdiB&originHeight=510&originWidth=1032&originalType=binary&ratio=1&status=done&style=none)
  4. 而拦截器 (`Interceptor`) 它是一个`Spring`组件,并由`Spring`容器管理,并不依赖`Tomcat`等容器,是可以单独使用的。不仅能应用在`web`程序中,也可以用于`Application`、`Swing`等程序中。<br />![](https://img-blog.csdnimg.cn/20200602172813157.png#id=L7xZZ&originHeight=483&originWidth=1227&originalType=binary&ratio=1&status=done&style=none)
  5. <a name="09ee2ef5"></a>
  6. #### 3、触发时机不同
  7. `过滤器` 和 `拦截器`的触发时机也不同,我们看下边这张图。<br />![](https://img-blog.csdnimg.cn/20200602173814901.png?#pic_center#id=xBz1u&originHeight=450&originWidth=568&originalType=binary&ratio=1&status=done&style=none)
  8. 过滤器`Filter`是在请求进入容器后,但在进入`servlet`之前进行预处理,请求结束是在`servlet`处理完以后。
  9. 拦截器 `Interceptor` 是在请求进入`servlet`后,在进入`Controller`之前进行预处理的,`Controller` 中渲染了对应的视图之后请求结束。
  10. <a name="28764b34"></a>
  11. #### 4、拦截的请求范围不同
  12. 在上边我们已经同时配置了过滤器和拦截器,再建一个`Controller`接收请求测试一下。

@Controller @RequestMapping() public class Test {

  1. @RequestMapping("/test1")
  2. @ResponseBody
  3. public String test1(String a) {
  4. System.out.println("我是controller");
  5. return null;
  6. }

}

  1. 项目启动过程中发现,过滤器的`init()`方法,随着容器的启动进行了初始化。<br />![](https://img-blog.csdnimg.cn/20200603152935516.png?#id=XkIQF&originHeight=473&originWidth=1003&originalType=binary&ratio=1&status=done&style=none)
  2. 此时浏览器发送请求,F12 看到居然有两个请求,一个是我们自定义的 `Controller` 请求,另一个是访问静态图标资源的请求。<br />![](https://img-blog.csdnimg.cn/2020060315412216.png#id=H09Lw&originHeight=179&originWidth=757&originalType=binary&ratio=1&status=done&style=none)
  3. 看到控制台的打印日志如下:
  4. 执行顺序 `Filter 处理中` -> `Interceptor 前置` -> `我是 controller` -> `Interceptor 处理中` -> `Interceptor 处理后`

Filter 处理中 Interceptor 前置 Interceptor 处理中 Interceptor 后置 Filter 处理中

  1. 过滤器`Filter`执行了两次,拦截器`Interceptor`只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对`Controller`中请求或访问`static`目录下的资源请求起作用。
  2. <a name="ebb1234a"></a>
  3. #### 5、注入 Bean 情况不同
  4. 在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些`service`服务。
  5. 下边我们分别在过滤器和拦截器中都注入`service`,看看有什么不同?

@Component public class TestServiceImpl implements TestService {

  1. @Override
  2. public void a() {
  3. System.out.println("我是方法A");
  4. }

}

  1. 过滤器中注入`service`,发起请求测试一下 ,日志正常打印出`“我是方法 A”`

@Autowired private TestService testService;

  1. @Override
  2. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  3. System.out.println("Filter 处理中");
  4. testService.a();
  5. filterChain.doFilter(servletRequest, servletResponse);
  6. }

Filter 处理中 我是方法A Interceptor 前置 我是controller Interceptor 处理中 Interceptor 后置

  1. 在拦截器中注入`service`,发起请求测试一下 ,竟然 TM 的报错了,`debug`跟一下发现注入的`service`怎么是`Null`啊?<br />![](https://img-blog.csdnimg.cn/20200603163633360.png?#id=TUgBZ&originHeight=277&originWidth=1025&originalType=binary&ratio=1&status=done&style=none)
  2. 这是因为加载顺序导致的问题,`拦截器`加载的时间点在`springcontext`之前,而`Bean`又是由`spring`进行管理。
  3. > 拦截器:老子今天要进洞房;<br />Spring:兄弟别闹,你媳妇我还没生出来呢!
  4. 解决方案也很简单,我们在注册拦截器之前,先将`Interceptor` 手动进行注入。**注意**:在`registry.addInterceptor()`注册的是`getMyInterceptor()` 实例。

@Configuration public class MyMvcConfig implements WebMvcConfigurer {

  1. @Bean
  2. public MyInterceptor getMyInterceptor(){
  3. System.out.println("注入了MyInterceptor");
  4. return new MyInterceptor();
  5. }
  6. @Override
  7. public void addInterceptors(InterceptorRegistry registry) {
  8. registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
  9. }

}

  1. <a name="c5217c33"></a>
  2. #### 6、控制执行顺序不同
  3. 实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。
  4. 过滤器用`@Order`注解控制执行顺序,通过`@Order`控制过滤器的级别,值越小级别越高越先执行。

@Order(Ordered.HIGHEST_PRECEDENCE) @Component public class MyFilter2 implements Filter {

  1. 拦截器默认的执行顺序,就是它的注册顺序,也可以通过`Order`手动设置控制,值越小越先执行。

@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor2()).addPathPatterns(“/“).order(2); registry.addInterceptor(new MyInterceptor1()).addPathPatterns(“/“).order(1); registry.addInterceptor(new MyInterceptor()).addPathPatterns(“/**”).order(3); }

  1. 看到输出结果发现,先声明的拦截器 `preHandle()` 方法先执行,而`postHandle()`方法反而会后执行。
  2. `postHandle()` 方法被调用的顺序跟 `preHandle()` 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

Interceptor1 前置 Interceptor2 前置 Interceptor 前置 我是controller Interceptor 处理中 Interceptor2 处理中 Interceptor1 处理中 Interceptor 后置 Interceptor2 处理后 Interceptor1 处理后

  1. **那为什么会这样呢?** 得到答案就只能看源码了,我们要知道`controller` 中所有的请求都要经过核心组件`DispatcherServlet`路由,都会执行它的 `doDispatch()` 方法,而拦截器`postHandle()``preHandle()`方法便是在其中调用的。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

  1. try {
  2. ...........
  3. try {
  4. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  5. String method = request.getMethod();
  6. boolean isGet = "GET".equals(method);
  7. if (isGet || "HEAD".equals(method)) {
  8. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
  11. }
  12. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  13. return;
  14. }
  15. }
  16. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  17. return;
  18. }
  19. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  20. if (asyncManager.isConcurrentHandlingStarted()) {
  21. return;
  22. }
  23. applyDefaultViewName(processedRequest, mv);
  24. mappedHandler.applyPostHandle(processedRequest, response, mv);
  25. }
  26. }
  27. ...........
  28. }
  1. 看看两个方法`applyPreHandle()``applyPostHandle()`具体是如何被调用的,就明白为什么`postHandle()``preHandle()` 执行顺序是相反的了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors[i]; if(!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } }

  1. return true;
  2. }

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = interceptors.length - 1; i >= 0; —i) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } } ```

发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()preHandle() 方法执行的顺序相反。
过滤器 和 拦截器 6个区别 - 图2

总结

我相信大部分人都能熟练使用滤器和拦截器,但两者的差别还是需要多了解下,不然开发中使用不当,时不时就会出现奇奇怪怪的问题,以上内容比较简单,新手学习老鸟复习,有遗漏的地方还望大家积极补充,如有理解错误之处,还望不吝赐教。


原创不易,燃烧秀发输出内容

习惯在 VX 看技术文章,想要获取更多 Java 资源的同学,可以关注我的公众号:程序员内点事,暗号:[666]
https://segmentfault.com/a/1190000022833940