springmvc拦截器是我们项目开发中用到的一个功能,常常用于对Handler进行预处理和后处理。本案例来演示一个较简单的springmvc拦截器的使用,并通过分析源码来探究拦截器的执行顺序是如何控制的。
1、springmvc拦截器使用
1.1 项目初始搭建
1.1.1 创建一个maven的war工程
该步骤不再截图说明
1.1.2 引入maven依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope></dependency></dependencies>
1.2.3 配置web.xml
配置springmvc核心控制器DispatcherServlet,由于需要加载springmvc.xml,所以需要创建一个springmvc.xml文件(文件参考源码附件)放到classpath下
<!-- 前端控制器(加载classpath:springmvc.xml 服务器启动创建servlet) --><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
1.2 拦截器开发
1.2.1 准备两个拦截器
两个拦截器分别命名为MyInterceptor1、MyInterceptor2
publicclassMyInterceptor1implementsHandlerInterceptor{publicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler){System.out.println("==1-1====前置拦截器1 执行======");returntrue;//ture表示放行}publicvoidpostHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView){System.out.println("==1-2=====后置拦截器1 执行======");}publicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){System.out.println("==1-3======最终拦截器1 执行======");}}
publicclassMyInterceptor2implementsHandlerInterceptor{publicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler){System.out.println("==2-1====前置拦截器2 执行======");returntrue;//ture表示放行}publicvoidpostHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView){System.out.println("==2-2=====后置拦截器2 执行======");}publicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){System.out.println("==2-3======最终拦截器2 执行======");}}
1.2.2 在springmvc.xml中拦截器
<!--配置拦截器--><mvc:interceptors><!--配置拦截器--><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.itheima.interceptor.MyInterceptor1"/></mvc:interceptor><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.itheima.interceptor.MyInterceptor2"/></mvc:interceptor></mvc:interceptors>
这两个拦截器拦截规则相同,并且配置顺序拦截器1在拦截器2之前!
1.3 测试拦截器效果
1.3.1 准备测试Controller
@ControllerpublicclassBizController{@RequestMapping("testBiz")publicStringshowUserInfo(Integer userId,Model model){System.out.println(">>>>>业务代码执行-查询用户ID为:"+ userId);User user =newUser(userId);
user.setName("宙斯");
model.addAttribute("userInfo",user);return"user_detail";}}
该controller会转发到user_detail.jsp页面
1.3.2 准备user_detail.jsp
<html><head><title>detail</title></head><body>
用户详情:
${userInfo.id}:${userInfo.name}<%System.out.print(">>>>>jsp页面的输出为:");%><%System.out.println(((User)request.getAttribute("userInfo")).getName());%></body></html>
1.3.3 测试效果
启动项目后,在地址栏访问/testBiz?userId=1,然后查看IDE控制台打印:
==1-1====前置拦截器1 执行========2-1====前置拦截器2 执行======>>>>>业务代码执行-查询用户ID为:1==2-2=====后置拦截器2 执行========1-2=====后置拦截器1 执行======>>>>>jsp页面的输出为:宙斯
==2-3======最终拦截器2 执行========1-3======最终拦截器1 执行======
通过打印日志发现,拦截器执行顺序是:
拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终
2、源码分析
经过测试发现拦截器执行顺序如下:
拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终
我们通过分析源码来探究下拦截器是如何执行的
2.1 DispatcherServlet
当浏览器发送/testBiz?userId=1的请求时,会经过DispatcherServlet的doDispatch方法,我们将其取出并观察其核心代码(省略非关键代码)
protectedvoiddoDispatch(HttpServletRequest request,HttpServletResponse response)throwsException{//...try{try{ModelAndView mv =null;Object dispatchException =null;try{
processedRequest =this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;//1.获取执行链
mappedHandler =this.getHandler(processedRequest);if(mappedHandler ==null){this.noHandlerFound(processedRequest, response);return;}//2.获取处理器适配器HandlerAdapter ha =this.getHandlerAdapter(mappedHandler.getHandler());//...//【3】.执行前置拦截器if(!mappedHandler.applyPreHandle(processedRequest, response)){return;}//4.执行业务handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if(asyncManager.isConcurrentHandlingStarted()){return;}this.applyDefaultViewName(processedRequest, mv);//【5】.执行后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);}catch(Exception var20){
dispatchException = var20;}catch(Throwable var21){
dispatchException =newNestedServletException("Handler dispatch failed", var21);}//【6】.处理页面响应,并执行最终拦截器this.processDispatchResult(processedRequest, response, mappedHandler, mv,(Exception)dispatchException);}catch(Exception var22){this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);}catch(Throwable var23){this.triggerAfterCompletion(processedRequest, response, mappedHandler,newNestedServletException("Handler processing failed", var23));}}finally{//...}}
代码中有关拦截器执行的位置我都添加了注释,其中注释中标识的步骤中,3、5、6步骤是拦截器的关键步骤
其中,第一步中"获取执行链",执行链内容可以通过debug调试查看内容:
可以看到我们自定义的两个拦截器按顺序保存
2.2 拦截器步骤解析
在doDispatch方法中,我们添加的注释的第【3】、【5】、【6】步骤是对拦截器的执行处理,现在分别来查看第【3】、【5】、【6】步骤执行的具体方法的源码
2.2.1 第【3】步骤
//3.执行前置拦截器中的详细代码booleanapplyPreHandle(HttpServletRequest request,HttpServletResponse response)throwsException{//获得本次请求对应的所有拦截器HandlerInterceptor[] interceptors =this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)){//按照拦截器顺序依次执行每个拦截器的preHandle方法.//并且,interceptorIndex值会一次 + 1 (该值是给后面的最终拦截器使用的)for(int i =0; i < interceptors.length;this.interceptorIndex = i++){HandlerInterceptor interceptor = interceptors[/color][i][color=black];//只要每个拦截器不返回false,则继续执行,否则执行最终拦截器if(!interceptor.preHandle(request, response,this.handler)){this.triggerAfterCompletion(request, response,(Exception)null);returnfalse;}}}//最终返回truereturntrue;}
我们可以看到拦截器的preHandler(前置处理)方法是按拦截器(拦截器1、拦截器2)顺序执行的,然后我们再来看步骤【5】
2.2.2 第【5】步骤
//5.执行后置拦截器voidapplyPostHandle(HttpServletRequest request,HttpServletResponse response,@NullableModelAndView mv)throwsException{//获得本次请求对应的所有拦截器HandlerInterceptor[] interceptors =this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)){//按倒叙执行每个拦截器的postHandle方法——所以我们看到先执行的拦截器2的postHandle,再执行拦截器1的postHandlefor(int i = interceptors.length -1; i >=0;--i){HandlerInterceptor interceptor = interceptors[/color][color=black];
interceptor.postHandle(request, response,this.handler, mv);}}}
会发现,后置处理是按照拦截器顺序倒叙处理的!
我们最后来看下最终拦截器
2.2.3 第【6】步骤
//执行的方法privatevoidprocessDispatchResult(HttpServletRequest request,HttpServletResponse response,@NullableHandlerExecutionChain mappedHandler,@NullableModelAndView mv,@NullableException exception)throwsException{//...if(mv !=null&&!mv.wasCleared()){//处理响应this.render(mv, request, response);if(errorView){WebUtils.clearErrorRequestAttributes(request);}}elseif(this.logger.isDebugEnabled()){this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '"+this.getServletName()+"': assuming HandlerAdapter completed request handling");}if(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()){if(mappedHandler !=null){//6、执行拦截器的最终方法们
mappedHandler.triggerAfterCompletion(request, response,(Exception)null);}}}
其中,有一个render()方法,该方法会直接处理完response。再后则是触发triggerAfterCompletion方法:
//6、执行拦截器的最终方法voidtriggerAfterCompletion(HttpServletRequest request,HttpServletResponse response,@NullableException ex)throwsException{HandlerInterceptor[] interceptors =this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)){//倒叙执行每个拦截器(interceptorIndex为前置拦截器动态计算)的afterCompletion方法for(int i =this.interceptorIndex; i >=0;--i){HandlerInterceptor interceptor = interceptors[/color][/i][color=black][i];try{
interceptor.afterCompletion(request, response,this.handler, ex);}catch(Throwable var8){
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);}}}}
由此可以看到,拦截器的最终方法的执行也是按照倒叙来执行的,而且是在响应之后。
3、总结
拦截器常用于初始化资源,权限监控,会话设置,资源清理等的功能设置,就需要我们对它的执行顺序完全掌握,我们通过源码可以看到,拦截器类似于对我们业务方法的环绕通知效果,并且是通过循环收集好的拦截器集合来控制每个拦截器方法的执行顺序,进而可以真正做到深入掌握拦截器的执行机制!
Spring MVC框架是什么?有什么特点?
Spring MVC是Spring提供的一个实现了Web MVC设计模式的轻量级Web框架。它与Struts2框架一样,都属于MVC框架,但其使用和性能等方面比Struts2更加优异。
Spring MVC具有如下特点:
是Spring框架的一部分,可以方便的利用Spring所提供的其他功能。
灵活性强,易于与其他框架集成。
提供了一个前端控制器DispatcherServlet,使开发人员无需额外开发控制器对象。
可自动绑定用户输入,并能正确的转换数据类型。
内置了常见的校验器,可以校验用户输入。如果校验不能通过,那么就会重定向到输入表单。
支持国际化。可以根据用户区域显示多国语言。
支持多种视图技术。它支持JSP、Velocity和FreeMarker等视图技术。
使用基于XML的配置文件,在编辑后,不需要重新编译应用程序。
除上述几个优点外,Spring MVC还有很多其他优点,由于篇幅有限,这里就不一一列举了。在接下来的学习中,读者会逐渐的体会到Spring MVC的这些优点。