首页 > 技术文章 > Spring生态系列文章 >

SpringMVC拦截器详解

更新时间:2018-10-08 | 阅读量(1,187)

##前言 Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts2(一般老项目使用)等。 SpringMVC中的Interceptor拦截器用于拦截Controller层接口,表现形式有点像Spring的AOP,但是AOP是针对单一的方法。Interceptor是针对Controller接口以及可以处理request和response对象。 下面,我们来看看SpringMVC中拦截器的使用及实现 ####HandlerInterceptor接口的定义 在该接口中,定义了一下三个方法 ``` public interface HandlerInterceptor { boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception; void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception; } ``` - preHandle: 在访问到达Controller之前执行,如果需要对请求做预处理,可以选择在该方法中完成 返回值为true:继续执行后面的拦截器或者Controller 返回值为false:不再执行后面的拦截器和Controller,并调用返回true的拦截器的afterCompletion方法 - postHandle: 在执行完Controller方法之后,渲染视图之前执行,如果需要对响应相关的数据进行处理,可以选择在该方法中完成 - afterCompletion: 调用完Controller接口,渲染View页面后调用。返回true的拦截器都会调用该拦截器的afterCompletion方法,顺序相反。 ####自定义拦截器和使用 自定义一个我们自己的拦截器非常简单,定义一个类,实现上面的接口,然后覆写对应的方法即可 当然,在实际开发中,我们一般选择继承该接口的实现类HandlerInterceptorAdapter来实现拦截器的定义,如下: ``` public class CheckLoginInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1:获取登录凭证 Employee emp = UserContext.getCurrentUser(); if(emp == null){ //没有登录 response.sendRedirect("/login.html"); return false; //终止请求继续往下执行 } return true; //放行 } } ``` 上面,我们定义了一个检查用户是否登录的拦截器,如果没有登录,跳转到登录页面,反之,放行继续访问目标资源 要让我们的拦截器被框架得知并管理,我们还需要在配置文件中做如下配置来注册拦截器 ``` ``` 如此,拦截器就能够在项目中部署起来 当用户发起请求相应资源的时候,会首先经过该拦截器的处理,防止用户在没有登录的情况下直接访问项目中的核心资源 ####原理解析 可以看出,拦截器在SpringMVC框架中实现是非常简单的,但是,大家一定要清楚一个道理 >当你觉得很轻松的时候,是有另外一些人替你负重前行 这个时候,是谁在为我们负重前行呢?当然是我们使用的SpringMVC框架了! 那么,框架这个时候都为我们做了哪些事情呢?请往下看: SpringMVC框架的入口是一个使用Servlet实现的前端控制器: - **DispatcherServlet** 我们的每次请求都会先经过这个入口的处理才能到达目标资源,所以,来看看这里都做了哪些事吧 该类中,最重要的一个方法是doDispatch,在这个方法中,完成了整个执行流程的任务分配 下面是该方法中的部分核心代码: ``` protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //返回 HandlerExecutionChain 其中包含了拦截器队列 mappedHandler = this.getHandler(processedRequest); if(mappedHandler == null || mappedHandler.getHandler() == null) { this.noHandlerFound(processedRequest, response); return; } //获取到适合处理当前请求的适配器,最终用来调用Controller中的方法 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); //调用拦截器链中所有拦截器的preHandle方法 if(!mappedHandler.applyPreHandle(processedRequest, response)) { //如果有拦截器的preHandle方法返回值为false,则结束该方法的执行 return; } //调用请求的Controller中的方法,获取到ModelAndView对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //调用拦截器链中所有拦截器的postHandle方法,和执行preHandle方法的顺序相反 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var19) { dispatchException = var19; } //处理视图渲染 this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception var20) { //如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法 this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20); } } ``` 1. mappedHandler = this.getHandler(processedRequest); 返回 HandlerExecutionChain 其中包含了拦截器队列 2. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); 获取到适合处理当前请求的适配器,最终用来调用Controller中的方法 3. mappedHandler.applyPreHandle(processedRequest, response) 调用拦截器链中所有拦截器的preHandle方法 4. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 调用请求的Controller中的方法,获取到ModelAndView对象 5. mappedHandler.applyPostHandle(processedRequest, response, mv); 调用拦截器链中所有拦截器的postHandle方法,和执行preHandle方法的顺序相反 6. this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 处理结果视图的渲染,简单说就是页面的跳转问题 7. this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20); 如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法 通过上面对DispatcherServlet中核心代码的分析,相信大家对拦截器的执行流程有了大致的理解 下面我们再对这个过程中的细节继续进行分析: - **获取拦截器** DispatcherServlet: ``` protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { HandlerExecutionChain handler; handler = hm.getHandler(request); return handler; } ``` AbstractHandlerMapping: ``` public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = this.getHandlerInternal(request); if(handler == null) { handler = this.getDefaultHandler(); } if(handler == null) { return null; } else { HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request); return executionChain; } } ``` AbstractHandlerMapping: 遍历所有的拦截器, 把所有匹配当前请求的所有拦截添加到拦截器队列中 ``` protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = handler instanceof HandlerExecutionChain?(HandlerExecutionChain)handler:new HandlerExecutionChain(handler); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); Iterator var5 = this.adaptedInterceptors.iterator(); while(var5.hasNext()) { HandlerInterceptor interceptor = (HandlerInterceptor)var5.next(); if(interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor; if(mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; } ``` MappedInterceptor: 如果请求资源路径为 /login.html 则排除当前拦截器 如果请求资源路径为 不在exclude-mapping中,且能够匹配 /** 路径, 则添加到拦截器队列 ``` public boolean matches(String lookupPath, PathMatcher pathMatcher) { PathMatcher pathMatcherToUse = this.pathMatcher != null?this.pathMatcher:pathMatcher; String[] var4; int var5; int var6; String pattern; if(this.excludePatterns != null) { var4 = this.excludePatterns; var5 = var4.length; for(var6 = 0; var6 < var5; ++var6) { pattern = var4[var6]; if(pathMatcherToUse.match(pattern, lookupPath)) { return false; } } } if(this.includePatterns == null) { return true; } else { var4 = this.includePatterns; var5 = var4.length; for(var6 = 0; var6 < var5; ++var6) { pattern = var4[var6]; if(pathMatcherToUse.match(pattern, lookupPath)) { return true; } } return false; } } ``` - **处理拦截器** ``` 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]; //调用拦截器中的preHandle方法 if(!interceptor.preHandle(request, response, this.handler)) { // 如果preHandler方法返回false,则触发afterCompletion方法的执行 this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } return true; } ``` ``` void applyPostHandle(HttpServletRequest request, HttpServletResponse response, 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]; //调用拦截器中的postHandle方法 interceptor.postHandle(request, response, this.handler, mv); } } } ``` 当前拦截器中preHandle方法如果返回true,则该方法会在下面几种情况的时候会执行 ①Controller正常执行,视图渲染后 ②程序有异常的时候 ③在任何拦截器preHandle方法返回false的时候 ``` void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = this.interceptorIndex; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[i]; try { //调用拦截器中的afterCompletion方法 interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable var8) { logger.error("HandlerInterceptor.afterCompletion threw exception", var8); } } } } ``` 可以看到,SpringMVC在执行这一系列的处理的时候,做了很多的细节处理,但是,在我们看源码的时候最好能够排除和功能无关的代码,这样有利于我们理解整个执行流程 所以,在上面的代码中,我仅仅将这个过程中比较重要的代码贴了出来, 不是完整的代码,如果要看完成的代码,请自行参考框架的源码学习,谢谢
叩丁狼学员采访 叩丁狼学员采访
叩丁狼头条 叩丁狼头条
叩丁狼在线课程 叩丁狼在线课程