点击“ 程序员内点事 ”关注,选择“ 设置星标 ”

坚持学习,好文每日送达!

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

毕竟这两种工具开发中用到的频率都相当高,应用起来也是比较简单的,可当我准备回复他的时候,竟然不知道从哪说起,支支吾吾了半天,场面炒鸡尴尬有木有,工作这么久一个基础问题答成这样,丢了大人了。平时觉得简单的知识点,但通常都不会太关注细节,一旦被别人问起来,反倒说不出个所以然来。

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

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

准备环境

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

1、过滤器 (Filter)

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

  • init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。「注意」:这个方法必须执行成功,否则过滤器会不起作用。

  • doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter

  • destroy():当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次

@Componentpublic 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 {

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

    @Override    public void destroy() {

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

2、拦截器 (Interceptor)

拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。

首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor 接口中也定义了三个方法。

  • preHandle() :这个方法将在请求处理之前进行调用。「注意」:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。

  • postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。「有意思的是」postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器  preHandle() 方法先执行,而postHandle()方法反而会后执行。

  • afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后,  DispatcherServlet 渲染了对应的视图之后执行。

@Componentpublic class MyInterceptor implements HandlerInterceptor {

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

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

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

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

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

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

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

@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {

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

我们不一样

过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。

1、实现原理不同

过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。

这里重点说下过滤器!

在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

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

ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法。

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

    private void internalDoFilter(ServletRequest request, ServletResponse response){    if (pos             //获取第pos个filter                ApplicationFilterConfig filterConfig = filters[pos++];                    Filter filter = filterConfig.getFilter();            ...            filter.doFilter(request, response, this);        }    }

}

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

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

        filterChain.doFilter(servletRequest, servletResponse);    }

2、使用范围不同

我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。而拦截器(Interceptor)  它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中。

3、触发时机不同

过滤器拦截器的触发时机也不同,我们看下边这张图。

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

4、拦截的请求范围不同

在上边我们已经同时配置了过滤器和拦截器,再建一个Controller接收请求测试一下。

@Controller@RequestMapping()public class Test {

    @RequestMapping("/test1")    @ResponseBody    public String test1(String a) {        System.out.println("我是controller");        return null;    }}

项目启动过程中发现,过滤器的init()方法,随着容器的启动进行了初始化。此时浏览器发送请求,F12 看到居然有两个请求,一个是我们自定义的 Controller 请求,另一个是访问静态图标资源的请求。看到控制台的打印日志如下:

执行顺序 :Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后

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

过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

5、注入Bean情况不同

在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。

下边我们分别在过滤器和拦截器中都注入service,看看有什么不同?

@Componentpublic class TestServiceImpl implements TestService {

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

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

@Autowired    private TestService testService;

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

        System.out.println("Filter 处理中");        testService.a();        filterChain.doFilter(servletRequest, servletResponse);    }
Filter 处理中我是方法AInterceptor 前置我是controllerInterceptor 处理中Interceptor 后置

在拦截器中注入service,发起请求测试一下 ,竟然TM的报错了,debug跟一下发现注入的service怎么是Null啊?这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。

拦截器:老子今天要进洞房;Spring:兄弟别闹,你媳妇我还没生出来呢!

解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。「注意」:在registry.addInterceptor()注册的是getMyInterceptor() 实例。

@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {

    @Bean    public MyInterceptor getMyInterceptor(){        System.out.println("注入了MyInterceptor");        return new MyInterceptor();    }

    @Override    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");    }}

6、控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。

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

拦截器默认的执行顺序,就是它的注册顺序,也可以通过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);    }

看到输出结果发现,先声明的拦截器  preHandle() 方法先执行,而postHandle()方法反而会后执行。

postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

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

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

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

        try {         ...........            try {

                // 获取可以执行当前Handler的适配器                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.                String method = request.getMethod();                boolean isGet = "GET".equals(method);                if (isGet || "HEAD".equals(method)) {                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());                    if (logger.isDebugEnabled()) {                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);                    }                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {                        return;                    }                }                // 注意: 执行Interceptor中PreHandle()方法                if (!mappedHandler.applyPreHandle(processedRequest, response)) {                    return;                }

                // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {                    return;                }                applyDefaultViewName(processedRequest, mv);

                // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】                mappedHandler.applyPostHandle(processedRequest, response, mv);            }        }        ...........    }

看看两个方法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 this.interceptorIndex = i++) {                HandlerInterceptor interceptor = interceptors[i];                if(!interceptor.preHandle(request, response, this.handler)) {                    this.triggerAfterCompletion(request, response, (Exception)null);                    return false;                }            }        }

        return true;    }
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() 方法执行的顺序相反。

总结

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


原创不易,「燃烧秀发输出内容」,如果你有一丢丢收获,点个 「在看」 或者 「转发」 鼓励一下哦!

整理了几百本各类技术电子书相送 ,送给小伙伴们。关公众号回复【666】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就扫码加入我们吧!

关注,迈开成长的第一步

tomcat 拦截指定url_一口气说出 过滤器 和 拦截器 6个区别,别再傻傻分不清了相关推荐

  1. 一口气说出 过滤器 和 拦截器 6个区别,别再傻傻分不清了

    周末有个小伙伴加我微信,向我请教了一个问题:老哥,「过滤器 (Filter) 和 拦截器 (Interceptor) 有啥区别啊?」 听到题目我的第一感觉就是:「简单」! 毕竟这两种工具开发中用到的频 ...

  2. 【学习日记2023.5.8】之 springboot案例之登录功能(会话技术_JWT令牌_过滤器_拦截器)

    文章目录 1. 案例-登录认证 1. 1登录功能 1.1.1 需求 1.1.2 接口文档 1.1.3 思路分析 1.1.4 功能开发 1.1.5 测试 1.1.6 全后端联调 1.2 登录校验 1.2 ...

  3. springboot _配置过滤器、拦截器、使用原生servlet

    配置过滤器.拦截器.使用原生servlet 过滤器和拦截器的根本区别 过滤器是servlet规范规定的,只能用于web程序中,而拦截器是在spring容器中,它不依赖servlet容器. 拦截器属于S ...

  4. 过滤器,拦截器,aop 比较

    Filter过滤器 过滤器拦截web访问url地址. 严格意义上讲,filter只是适用于web中,依赖于Servlet容器,利用Java的回调机制进行实现. Filter过滤器:和框架无关,可以控制 ...

  5. Filter过滤器的拦截路径配置

    Filter的介绍 拦截路径配置 拦截所有资源:/* 具体资源路径:/myServlet/demo1 拦截目录:/myServlet/* 后缀名被拦截:*.jsp Filter的介绍 拦截路径配置 过 ...

  6. 过滤器跟拦截器的区别

    一.拦截器与过滤器的区别总结 1. 原理不同:拦截器是基于java的反射机制的,而过滤器是基于函数回调. 2. 依赖容器:拦截器不依赖与servlet容器,过滤器依赖与servlet容器. 3. 作用 ...

  7. spring 两次进入拦截器_过滤器和拦截器的 6 个区别,别再傻傻分不清了

    点击上方 肉眼品世界,选择 设为星标 深度价值体系传递 作者 :程序员内点事 来源 :toutiao.com/i6834310440495874563 毕竟这两种工具开发中用到的频率都相当高,应用起来 ...

  8. JavaWeb中监听器Listener+过滤器filter+拦截器interceptor区别

    JavaWeb中监听器Listener+过滤器filter+拦截器interceptor区别 如果从整个项目中看,一个servlet请求的执行过程就变成了这样context-param–>lis ...

  9. Struts2 过滤器与拦截器

    学习Struts2时,发现有过滤器和拦截器,他们貌似都是一样的功能,但是为什么会有2个不同的名称呢?肯定是有区别的,所以打算自己整理一下. 过滤器,是在java web中,你传入的request,re ...

最新文章

  1. 我的一次被骗去培训班狗血的经历
  2. 梁迪:我为MVP骄傲,《微软最有价值专家奖励计划介绍》附专题视频
  3. 多路平衡查找树 --- B(B-)树
  4. php测试系统登录超时,thinkphp后台检测用户登录超时的实现方法
  5. 贝壳宣布内部调查实质性完成
  6. JAVA学习笔记-this隐式参数
  7. GDB 多线程调试:只停止断点的线程,其他线程任然执行; 或只运行某些线程 其他线程中断...
  8. Centos7 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon run
  9. 网易老司机花式刷屏,腾讯爸爸欲教其做人,最终结局...
  10. Python IndentationError: expected an indented block
  11. 戴尔▪卡耐基《人性的弱点》阅读笔记(1)
  12. 【单片机毕业设计】【mcuclub-cl-022】基于单片机的隧道检测的设计
  13. 2021年下半年信息系统项目管理师下午真题及答案解析
  14. python again_It’s really(wonder)________ to see you here again in Beijing.
  15. 实战内容(13)- Invalid audio stream. Exactly one MP3 audio stream is required.
  16. vc 调用webservice
  17. 博士申请 | 美国密歇根州立大学ACTION Lab招收CV/ML方向全奖博士生
  18. TensorFlow神经网络学习笔记
  19. CDH6.3配置安装实操
  20. 计算机视觉(CV)方向今年招聘情况怎么样?是否已经人才过剩?

热门文章

  1. java两种传参,有关java参数的两种传递机制
  2. linux make java版本,告诉make是否在Windows或Linux上运行
  3. cad lisp 二次抛物线_学习CAD的五个段位,你是青铜还是王者?
  4. Gerrit的用法及与gitlab的区别
  5. IAR切BANK--命令连接器文件xcl格式说明
  6. 解决写文档对于图片边框的强迫症
  7. Makefile文件编写规则
  8. 修改ant design vue中的Icon图标颜色
  9. [react] react中的setState和replaceState的区别是什么?
  10. React开发(243):dva概念7subscription