本文来说下Spring拦截器和过滤器的区别

文章目录

  • 概述
  • 拦截器和过滤器介绍
    • 过滤器 (Filter)
    • 拦截器 (Interceptor)
    • 主要区别
  • 拦截器和过滤器的区别
    • 实现原理不同
    • 使用范围不同
    • 触发时机不同
    • 拦截的请求范围不同
    • 注入Bean情况不同
    • 控制执行顺序不同
  • 本文小结

概述

拦截器和过滤器

filter和拦截器的功能都是拦截,filter拦截的目标是servlet的执行,而拦截器拦截的是Spring MVC定义的概念,叫handler(常见的就是我们用RequestMapping定义出来的HandlerMethod)。觉得它相似是因为Spring的handler就是DispatcherServlet使用的,而后者就是一个servlet。filter包围着dispatcherServlet,而它自己也想去执行一个目标handler,并在执行周围包裹着拦截器,好处是拦截器可以被容器管理,从而获得被容器赋予的能力,而filter不行(这样一想和代理filter使用applicationFilterChain很类似,只是拦截器更贴近执行业务的方法)。没有什么其它更本质上的区别。


拦截器和过滤器介绍

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

过滤器 (Filter)

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

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

使用拦截器的代码


@Component
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("Filter 前置");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("Filter 处理中");filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {System.out.println("Filter 后置");}
}

拦截器 (Interceptor)

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

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

  • preHandle() :这个方法将在请求处理之前进行调用。「注意」:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
  • postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 「有意思的是」:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
  • afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。

使用过滤器的代码

@Component
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("Interceptor 前置");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("Interceptor 处理中");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("Interceptor 后置");}
}

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


@Configuration
public class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {//  /**表示拦截所有的路径registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");}
}

主要区别

(1)拦截器是基于java的反射机制的,而过滤器是基于函数回调。

(2)拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。

(3)拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。

(4)拦截器可以访问action上下文、值栈里的对象,而过滤器不能。

(5)在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。


拦截器和过滤器的区别

下面来详细说说二者之间的区别

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

实现原理不同

过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于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 {@Overridepublic void doFilter(ServletRequest request, ServletResponse response) {...//省略internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request, ServletResponse response){if (pos < n) {//获取第pos个filter    ApplicationFilterConfig filterConfig = filters[pos++];        Filter filter = filterConfig.getFilter();...filter.doFilter(request, response, this);}}}

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

   @Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {filterChain.doFilter(servletRequest, servletResponse);}

使用范围不同

我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。


而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。


触发时机不同

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


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

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


拦截的请求范围不同

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

@Controller
@RequestMapping()
public class Test {@RequestMapping("/test1")@ResponseBodypublic 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目录下的资源请求起作用。


注入Bean情况不同

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

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

@Component
public class TestServiceImpl implements TestService {@Overridepublic void a() {System.out.println("我是方法A");}
}

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

@Autowired
private TestService testService;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("Filter 处理中");testService.a();filterChain.doFilter(servletRequest, servletResponse);}

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

在拦截器中注入service,发起请求测试一下 ,竟然TM的报错了,debug跟一下发现注入的service怎么是Null啊?

这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。

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

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {@Beanpublic MyInterceptor getMyInterceptor(){System.out.println("注入了MyInterceptor");return new MyInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");}
}

控制执行顺序不同

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

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

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

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

 @Overridepublic 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 前置
我是controller
Interceptor 处理中
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 < interceptors.length; 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() 方法执行的顺序相反。


本文小结

本文详细介绍了spring中拦截器和过滤器的区别。我相信大部分人都能熟练使用滤器和拦截器,但两者的差别还是需要多了解下,不然开发中使用不当,时不时就会出现奇奇怪怪的问题。

Spring拦截器和过滤器的区别相关推荐

  1. 详解拦截器和过滤器的区别

    拦截器和过滤器的区别 过滤器和拦截器的区别: ①拦截器是基于java的反射机制的,而过滤器是基于函数回调. ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器. ③拦截器只能对acti ...

  2. java 拦截器和过滤器的区别

    介绍 在 Java Web 应用程序中,拦截器和过滤器是两种不同的机制,用于在请求/响应处理过程中进行拦截和过滤.两者都可以用来在请求到达目标资源之前对其进行预处理.修改或拦截. 但是,拦截器和过滤器 ...

  3. springboot拦截器和过滤器的区别与使用

    拦截器与过滤器的区别 spring的拦截器与servlet的filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查.日志记录等,不同的是: 适用范围不同:filter是servle ...

  4. 拦截器和过滤器的区别 -- 简单分析篇

    过滤器,是在Java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如过滤掉非法u ...

  5. 大剑无锋之拦截器和过滤器的区别【面试推荐】

    ①拦截器是基于java的反射机制的,而过滤器是基于函数回调. ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器. ③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求 ...

  6. Spring 拦截器和过滤器中自动注入为 null 的原因及解决方案

    起因 开发过程中在过滤器(filter)中注入Bean出现空指针异常,通过查找资料了解空指针的原因,特此记录. 问题分析 由于其他bean在service,controller层注入一点问题也没有,开 ...

  7. Spring—拦截器和过滤器介绍

    文章目录 介绍 过滤器(Filter) 拦截器(Interceptor) 包含关系 比较 关注微信公众号:CodingTechWork,一起学习进步. 介绍 过滤器(Filter) 过滤器是Java ...

  8. 关于SSM框架设置拦截器和过滤器

    我们知道拦截器和过滤器都是在项目中起到拦截过滤请求的功能,所以可能在设置的时候会傻傻分不清.这里我们先来比较它们的区别. 过滤器Filter是JavaEE标准,在Servlet的规范中定义的,是Ser ...

  9. 区分Java拦截器和过滤器

    今天带大家分析java拦截器和过滤器的区别,文中有非常详细的解释说明,对正在学习java的小伙伴们有很好的帮助,需要的朋友可以参考下 一.过滤器(filter) 过滤器处于客户端与Web资源(Serv ...

  10. struts2中拦截器和过滤器的比较

    拦截器和过滤器的区别: 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3.拦截器只能对action请求起作用,而 ...

最新文章

  1. COM原理及应用之COM特性
  2. ubuntu16.04 + kinetic +turtlebot2配置
  3. java生成图片url_Java Springboot如何基于图片生成下载链接
  4. Linux/AIX上部署VNC Server
  5. PM_我们是怎么做Code Review的
  6. stm32 c语言 位带,我对STM32所用位带操作宏的超详细剖析、优势分析及应用推广探索研究(持续更新,欢迎讨论交流)...
  7. 主板电源开关接口图解_【转】图解:各种主板接线方法 主板电线接法(电源开关、重启等)...
  8. TIA PORTAL博途中进行网络组态时,拓扑视图要不要组态,有什么作用?
  9. 大数据可视化--全球分布散点图
  10. 高性能网站建设进阶指南:Web开发者性能优化最佳实践 pdf扫描版
  11. 列表页进入详情页再返回列表页时,显示默认第一页的bug修复
  12. AiBote 2022 新研发的自动化框架,支持 Android 和 Windows 系统。速度非常快
  13. gym库的基本使用方法
  14. Fraction分数类II
  15. Codeforces Round #781 CF1665 CDE
  16. Excel除了复制粘贴,更快速的将单元格区域的公式转换成数值?
  17. 宋宝华:让Linux的段错误(segmentation fault)不再是一个错误
  18. excel2json 一个excel转json的工具(开源)
  19. 从软件生命周期看应用安全(网络安全)
  20. Door to Door 日剧改编 励志

热门文章

  1. 服务器突然c盘的temp文件夹,c盘里突然出了很多TEMP文件?
  2. 金士顿U盘修复工具Kingston Format Utility
  3. 不用u盘安装linux真机,无需u盘和光盘安装linux
  4. 遇到电脑中病毒了怎么办
  5. ActivityManager Displayed 源码位置
  6. 小程序按钮如何制作?
  7. 2020微博热点数据简析
  8. LoadRunner教程(13)-LoadRunner 服务水平协议
  9. 学习码 滚动码 固定码 有什么区别重码数,编码容量滚动码的原理
  10. 浙江工业大学计算机类专业是几年,浙江工业大学计算机类专业培养计划