本文收录在我的博客: www.chengxy-nds.top,同进步
周末有个小伙伴加我微信,向我请教了一个问题:老哥,过滤器 (Filter
) 和 拦截器 (Interceptor
) 有啥区别啊? 听到题目个人第一感受就是:简单!javascript
毕竟这两种工具开发中用到的频率都至关高,应用起来也是比较简单的,可当我准备回复他的时候,居然不知道从哪提及,支支吾吾了半天,场面炒鸡尴尬有木有,工做这么久一个基础问题答成这样,丢了大人了。
平时以为简单的知识点,但一般都不会太关注细节,一旦被别人问起来,反倒说不出个因此然来。java
归根结底,仍是对这些知识了解的不够,一直停留在会用的阶段,以致于如今一看就会一说就废!这是典型基础不扎实的表现,哎·~,其实我也就是个虚胖!程序员
知耻然后勇,下边结合实践,更直观的来感觉一下二者到底有什么不一样?web
咱们在项目中同时配置 拦截器
和 过滤器
。spring
过滤器的配置比较简单,直接实现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 { System.out.println("Filter 处理中"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("Filter 后置"); } }
拦截器它是链式调用,一个应用中能够同时存在多个拦截器Interceptor
, 一个请求也能够触发多个拦截器 ,而每一个拦截器的调用会依据它的声明顺序依次执行。数组
首先编写一个简单的拦截器处理类,请求的拦截是经过HandlerInterceptor
来实现,看到HandlerInterceptor
接口中也定义了三个方法。浏览器
preHandle()
:这个方法将在请求处理以前进行调用。注意:若是该方法的返回值为false
,将视为当前请求结束,不只自身的拦截器会失效,还会致使其余的拦截器也再也不执行。postHandle()
:只有在 preHandle()
方法返回值为true
时才会执行。会在Controller 中的方法调用以后,DispatcherServlet 返回渲染视图以前被调用。 有意思的是:postHandle()
方法被调用的顺序跟 preHandle()
是相反的,先声明的拦截器 preHandle()
方法先执行,而postHandle()
方法反而会后执行。afterCompletion()
:只有在 preHandle()
方法返回值为true
时才会执行。在整个请求结束以后, DispatcherServlet 渲染了对应的视图以后执行。@Component public 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 后置"); } }
将自定义好的拦截器处理类进行注册,并经过addPathPatterns
、excludePathPatterns
等属性设置须要拦截或须要排除的 URL
。微信
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); } }
过滤器 和 拦截器 均体现了AOP
的编程思想,均可以实现诸如日志记录、登陆鉴权等功能,但两者的不一样点也是比较多的,接下来一一说明。app
过滤器和拦截器 底层实现方式大不相同,过滤器
是基于函数回调的,拦截器
则是基于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 < n) { //获取第pos个filter ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); ... filter.doFilter(request, response, this); } } }
而每一个xxxFilter
会先执行自身的 doFilter()
过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse)
,也就是回调ApplicationFilterChain
的doFilter()
方法,以此循环执行实现函数回调。
@Override public 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") @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
目录下的资源请求起做用。
在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑不免会引入一些service
服务。
下边咱们分别在过滤器和拦截器中都注入service
,看看有什么不一样?
@Component public 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 处理中 我是方法A Interceptor 前置 我是controller Interceptor 处理中 Interceptor 后置
在拦截器中注入service
,发起请求测试一下 ,居然TM的报错了,debug
跟一下发现注入的service
怎么是Null
啊?
这是由于加载顺序致使的问题,拦截器
加载的时间点在springcontext
以前,而Bean
又是由spring
进行管理。
拦截器:老子今天要进洞房;
Spring:兄弟别闹,你媳妇我还没生出来呢!
解决方案也很简单,咱们在注册拦截器以前,先将Interceptor
手动进行注入。注意:在registry.addInterceptor()
注册的是getMyInterceptor()
实例。
@Configuration public 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("/**"); } }
实际开发过程当中,会出现多个过滤器或拦截器同时存在的状况,不过,有时咱们但愿某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。
过滤器用@Order
注解控制执行顺序,经过@Order
控制过滤器的级别,值越小级别越高越先执行。
@Order(Ordered.HIGHEST_PRECEDENCE) @Component public 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 前置 我是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()
方法执行的顺序相反。
我相信大部分人都能熟练使用滤器和拦截器,但二者的差异仍是须要多了解下,否则开发中使用不当,时不时就会出现奇奇怪怪的问题,以上内容比较简单,新手学习老鸟复习,有遗漏的地方还望你们积极补充,若有理解错误之处,还望不吝赐教。
原创不易,燃烧秀发输出内容
习惯在VX看技术文章,想要获取更多Java资源的同窗,能够关注个人公众号: 程序员内点事,暗号:[ 666]