Spring MVC请求处理流程分析
一、简介
Spring MVC框架在工作中经常用到,配置简单,使用起来也很方便,很多书籍和博客都有介绍其处理流程,但是,对于其原理,总是似懂非懂的样子。我们做技术,需要做到知其然,还要知其所以然。今天我们结合源码来深入了解一下Spring MVC的处理流程。
以上流程图是Spring MVC的处理流程(参考:spring-mvc-flow-with-example),原作者对流程的解释如下:
Step 1: First request will be received by DispatcherServlet. Step 2: DispatcherServlet will take the help of HandlerMapping and get to know the Controller class name associated with the given request. Step 3: So request transfer to the Controller, and then controller will process the request by executing appropriate methods and returns ModelAndView object (contains Model data and View name) back to the DispatcherServlet. Step 4: Now DispatcherServlet send the model object to the ViewResolver to get the actual view page. Step 5: Finally DispatcherServlet will pass the Model object to the View page to display the result.
针对以上流程,这里需要更加详细一点:
1、请求被web 容器接收,并且根据contextPath将请求发送给DispatcherServlet
2、DispatcherServlet接收到请求后,会设置一些属性(localeResolver、themeResolver等等),在根据request在handlerMappings中查找对应的HandlerExecutionChain;然后根据HandlerExecutionChain中的handler来找到HandlerAdapter,然后通过反射来调用handler中的对应方法(RequestMapping对应的方法)
3、handler就是对应的controller,调用controller中的对应方法来进行业务逻辑处理,返回ModelAndView(或者逻辑视图名称)
4、ViewResolver根据逻辑视图名称、视图前后缀,来获取实际的逻辑视图
5、获取实际视图之后,就会使用model来渲染视图,得到用户实际看到的视图,然后返回给客户端。
二、Demo样例
我们运行一个小样例(github地址:https://github.com/yangjianzhou/spring-mvc-demo)来了解Spring MVC处理流程,项目结构如下:
web.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>smart</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>smart</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
smart-servlet.xml的内容如下:
<context:component-scan base-package="com.iwill.mvc"/> <!-- 在使用Excel PDF的视图时,请先把这个视图解析器注释掉,否则产生视图解析问题--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="100" p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="/WEB-INF/views/" p:suffix=".jsp"/>
UserController.java的代码如下:
package com.iwill.mvc; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/user") public class UserController { Logger logger = Logger.getLogger(UserController.class); @RequestMapping("register") public String register() { logger.info("invoke register"); return "user/register"; } @RequestMapping(method = RequestMethod.POST) public ModelAndView createUser(User user) { ModelAndView mav = new ModelAndView(); mav.setViewName("user/createSuccess"); mav.addObject("user", user); return mav; } }
三、请求接收
DispatcherServlet的类继承关系如下:
可以看出,DispatcherServlet是一个HttpServlet,因此,它可以处理http请求。
在浏览器中输入http://localhost:8080/spring-mvc-demo/user/register,因为在web服务器上配置了spring-mvc-demo的contextPath为spring-mvc-demo,所以/spring-mvc-demo/user/register的请求就会被DispatcherServlet处理,请求处理路径如下:
请求由tomcat传递给了DispatcherServlet了,DispatcherServlet接收后,就开始自己的特殊处理了。
红框中是Spring MVC自己特有的逻辑,主要是与视图、主题有关。
接下来的主要处理逻辑在org.springframework.web.servlet.DispatcherServlet#doDispatch中:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // 根据request在handlerMappings中获取HandlerExecutionChain mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } //根据handler在handlerAdapters中获取HandlerAdapter 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()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { //适配器调用实际的handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //逻辑视图名转换为物理视图名,并进行视图渲染 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
首先获取HandlerExecutionChain(入口:mappedHandler = getHandler(processedRequest, false);),方法如下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
之后就是根据handler获取HandlerAdapter(入口:HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())):
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
handler适配器调用handler的方法(入口:mv = ha.handle(processedRequest, response, mappedHandler.getHandler())):
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ServletHandlerMethodResolver methodResolver = getMethodResolver(handler); Method handlerMethod = methodResolver.resolveHandlerMethod(request); ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver); ServletWebRequest webRequest = new ServletWebRequest(request, response); ExtendedModelMap implicitModel = new BindingAwareModelMap(); Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel); ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest); methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest); return mav; }
通过Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel)会调用底层的方法:
红框中,通过反射调用UserController的register方法。这样请求就被传递到了实际的controller方法了。
四、响应返回
UserController#register处理后,就返回逻辑视图名:user/register。在org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodInvoker#getModelAndView中,就会将String转化为ModelAndView:
在org.springframework.web.servlet.DispatcherServlet#render中,就会将逻辑视图ModelAndView转化物理视图。
resolveViewName就是使用ViewResolver来获取物理视图名:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; }
物理视图名会被缓存,不需要重复解析,提高性能。
这里就是prefix和suffix的用途了,用于定位实际视图。
获取到了物理视图之后,就进行视图渲染了。
针对jsp格式的视图,我们配置的view是org.springframework.web.servlet.view.JstlView,渲染过程就是将model中的值set到request的attribute中,之后就是使用jsp自己的规则来显示jsp文件就好。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
什么,你的ThreadLocal内存泄漏了?
微信公众号:IT一刻钟。大型现实非严肃主义现场,一刻钟与你分享优质技术架构与见闻,做一个有剧情的程序员。 关注可第一时间了解更多精彩内容,定期有福利相送哟。 又是一个风和日丽的早上。 这天小美遇到了一个难题。 原来小美在做用户服务鉴权的时候,需要根据每个请求获取token: //获取认证信息 Authentication authentication = tokenProvider.getAuthentication(jwt); //设置认证信息 SecurityContext.setAuthentication(authentication); 然后经过层层的调用,在业务代码里根据认证信息进行权限的判断,也就是鉴权。 小美心里琢磨着,如果每个方法参数中都传递SecurityContext信息,就显的太过冗余,而且看着也丑陋。 那么怎么才能隐式传递参数呢? 这个当然难不倒小美,她决定用ThreadLocal来传递这个变量: class SecurityContextHolder { private static final ThreadLocal<Securit...
- 下一篇
结构型模式:适配器模式
文章首发: 结构型模式:适配器模式 七大结构型模式之一:适配器模式。 简介 姓名 :适配器模式 英文名 :Adapter Pattern 价值观 :老媒人,牵线搭桥 个人介绍 : Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 (来自《设计模式之禅》) 你要的故事 大家有买过港式的 Apple 产品么?在深圳的同学估计买过,毕竟港式的 Apple 产品基本比国内便宜 500 以上。我手机和平板都是在香港买的,买来后这充电器是没法直接充电的,因为港版的电子产品都是英式的插头,而咱们国内是中式的,所以用上港版电子产品的同学免不了要用上这么一个转换器:将英式的插孔转为中式的插孔,方可插入咱家里的插座充电。...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,CentOS8安装Elasticsearch6.8.6
- 2048小游戏-低调大师作品
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2全家桶,快速入门学习开发网站教程