当前位置: 首页 > 科技观察

一口气说出过滤器和拦截器的6个区别,别再迷惑了

时间:2023-03-17 20:43:24 科技观察

周末有朋友加我微信,问我一个问题:大哥,Filter和Interceptor有什么区别?听到这个标题我的第一印象是:“简单”!毕竟这两个工具在开发中的使用频率都挺高的,应用也比较简单,但是我正要回复他的时候却没有不知道从哪里开始。愣了半天,场面很尴尬,这么基础的题,你这么久都答不上来,你就迷路了。我通常会想到简单的知识点,但通常不会太注意细节。曾经被别人问过,我也说不出原因。说到底还是对这些知识了解不够,一直停留在会用的阶段,以至于现在“见了就弃”!这是基础薄弱的典型表现。哎~,其实我只是一只Puffy!知耻而后勇。下面让我们结合实践,更直观地感受一下两者的区别。为了准备环境,我们在项目中同时配置拦截器和过滤器。1.过滤器(Filter)过滤器的配置比较简单。可以直接实现Filter接口,也可以通过@WebFilter注解拦截具体的URL。可以看到Filter接口中定义了三个方法。init():该方法在容器开始初始化filter时被调用,在Filter的整个生命周期中只会被调用一次。【注意】:该方法必须执行成功,否则过滤器将不起作用。doFilter():容器中的每一个请求都会调用该方法,FilterChain用于调用下一个过滤器Filter。destroy():当容器销毁过滤器实例时调用此方法。一般在方法中销毁或关闭资源,在过滤器的整个生命周期中只会调用一次。@ComponentpublicclassMyFilterimplementsFilter{@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{System.out.println("过滤器前置");}@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{System.out.println("过滤器处理中");filterChain.doFilter(servletRequest,servletResponse);}@Overridepublicvoiddestroy(){System.out.println("Filterpost");}}2.拦截器(Interceptor)拦截器是一个链式调用。一个应用中可以同时存在多个拦截器,一个请求也可以触发多个拦截器,每个拦截器的调用会按照声明的先后顺序依次执行。首先写一个简单的拦截器处理类。请求的拦截是通过HandlerInterceptor实现的。可以看到HandlerInterceptor接口中也定义了三个方法。preHandle():在处理请求之前将调用此方法。“注意”:如果该方法的返回值为false,则视为本次请求结束,不仅自身拦截器失效,还会导致其他拦截器不再执行。postHandle():只有当preHandle()方法的返回值为真时才会执行。将在Controller中的方法调用之后,但在DispatcherServlet返回以呈现视图之前调用。“有趣的事”:postHandle()方法的调用顺序与preHandle()相反。先声明的拦截器preHandle()方法先执行,而postHandle()方法后执行。afterCompletion():只有当preHandle()方法的返回值为真时才会执行。整个请求结束后,DispatcherServlet在渲染对应的视图后执行。@ComponentpublicclassMyInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{System.out.println("Interceptor前置");returntrue;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{System.out.println("拦截器处理");}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{System.out.println("Interceptorpost");}}注册自定义拦截器处理类,并通过addPathPatterns、excludePathPatterns等属性设置URL需要拦截或排除的。@ConfigurationpublicclassMyMvcConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newMyInterceptor()).addPathPatterns("/**");registry.addInterceptor(newMyInterceptor1()).addPathPatterns("/**");}}两者过滤器和拦截器体现了AOP的编程思想,都可以实现日志记录和登录认证等功能,但是两者之间有很多区别,接下来会一一说明。1、实现原理不同过滤器和拦截器的底层实现方式有很大的不同。过滤器基于函数回调,拦截器基于Java的反射机制(动态代理)实现。在这里,我们专注于过滤器!在我们自定义的过滤器中,我们会实现一个doFilter()方法,它有一个FilterChain参数,但实际上它是一个回调接口。ApplicationFilterChain是它的实现类,这个实现类内部还有一个doFilter()方法,是一个回调方法。publicinterfaceFilterChain{voiddoFilter(ServletRequestvar1,ServletResponsevar2)throwsIOException,ServletException;}ApplicationFilterChain可以得到我们自定义的xxxFilter类,在其内部回调方法doFilter()中调用每个自定义的xxxFilter过滤器,执行doFilter()方法。publicfinalclassApplicationFilterChainimplementsFilterChain{@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse){...//省略internalDoFilter(request,response);}privatevoidinternalDoFilter(ServletRequestrequest,ServletResponseresponse){if(pos拦截器预处理->我是控制器->拦截器处理->拦截器后处理Filter处理InterceptorPre-Interceptor处理InterceptorPost-Filter处理的filter过滤器执行两次,拦截器Interceptor只执行一次。这是因为过滤器几乎可以作用于所有进入容器的请求,而拦截器只作用于Controller中的请求或者访问静态目录下的资源请求。5、注入bean的情况不同。在实际业务场景中,应用于过滤器或拦截器时,不可避免地会引入一些服务来处理业务逻辑。接下来我们给过滤器和拦截器都注入service看看有什么区别?@ComponentpublicclassTestServiceImplimplementsTestService{@Overridepublicvoida(){System.out.println("IammethodA");}}将service注入filter,发起请求测试,日志正常打印“IammethodA”。@AutowiredprivateTestServicetestService;@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{System.out.println("过滤处理");testletService.a();方法AInterceptor前面我是controllerInterceptor处理,Interceptor在拦截器后注入服务,发起请求测试,TM报错,调试发现注入的服务为Null?这是加载顺序导致的问题,拦截器在springcontext之前加载,bean由spring管理。?拦截者:我今天要入洞房;春:哥别闹了,我还没生你媳妇呢!?解决方法也很简单。在注册拦截器之前,我们先手动注入Interceptor。“注意”:getMyInterceptor()实例注册在registry.addInterceptor()中。@ConfigurationpublicclassMyMvcConfigimplementsWebMvcConfigurer{@BeanpublicMyInterceptorgetMyInterceptor(){System.out.println("InjectedintoMyInterceptor");returnnewMyInterceptor();}@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.add"Pathter(getMyInterceptor)."Interceptor(}}6.不同的控制执行顺序在实际开发过程中,会同时存在多个过滤器或者拦截器,但是有时候我们希望一个过滤器或者拦截器先执行,这就涉及到它们的执行顺序,过滤器使用@Order注解来控制执行顺序,过滤器的级别由@Order控制,值越小,级别越高,最先执行。order,也可以通过Order手动设置和控制,值越小越早执行可爱的。@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newMyInterceptor2()).addPathPatterns("/**").order(2);registry.addInterceptor(newMyInterceptor1()).addPathPatterns("/**").order(1);registry.addInterceptor(newMyInterceptor()).addPathPatterns("/**").order(3);}看输出结果,发现先声明的拦截器的preHandle()方法先执行,而postHandle()方法改为在会后执行。postHandle()方法的调用顺序其实和preHandle()是相反的!如果在实际开发中对执行顺序有严格要求,则需要特别注意这一点。Interceptor1frontInterceptor2frontInterceptorfront我是控制器InterceptorprocessingInterceptor2processingInterceptor1processingInterceptorpostInterceptor2processingAfterInterceptor1processing“那这是为什么?”要想得到答案,只能看源码了,我们要知道controller中的所有请求都必须通过核心组件DispatcherServlet进行路由,其doDispatch()方法会被执行,拦截器postHandle()和preHandle()方法在其中被调用。protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{try{............try{//获取可以执行当前Handler的适配器HandlerAdapterha=getHandlerAdapter(mappedHandler.getHandler());//处理last-modifiedheader,ifsupportedbythehand.Stringmethod=request.getMethod();booleanisGet="GET".equals(method);if(isGet||"HEAD".equals(method)){longlastModified=ha.getLastModified(请求,mappedHandler.getHandler());if(logger.isDebugEnabled()){logger.debug("Last-Modifiedvaluefor["+getRequestUri(request)+"]is:"+lastModified);}if(newServletWebRequest(request,response).checkNotModified(lastModified)&&isGet){return;}}//注意:执行Interceptor中的PreHandle()方法if(!mappedHandler.applyPreHandle(processedRequest,response)){return;}//注意:执行Handle【包括我们的业务逻辑,当抛出异常时会Tryandcatch】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()的执行顺序是相反的booleanapplyPreHandle(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{HandlerInterceptor[]interceptors=this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)){for(inti=0;i=0;--i){HandlerInterceptorinterceptor=interceptors[i];interceptor.postHandle(request,response,this.handler,mv);}}}发现两个方法中,在调用拦截器数组HandlerInterceptor[]时,循环的顺序是相反的。..,导致postHandle()和preHandle()方法的执行顺序相反。综上所述,相信大部分人都可以熟练使用过滤器和拦截器,但是两者的区别还是需要多了解一下,否则在开发中使用不当,时不时会出现奇怪的问题。小鸟正在复习,希望大家积极补缺。如有理解上的错误,还望大家不吝赐教。

最新推荐
猜你喜欢