Spring MVC请求处理流程分析
2019独角兽企业重金招聘Python工程师标准>>>
一、简介
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/javaeehttp://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中获取HandlerExecutionChainmappedHandler = getHandler(processedRequest, false);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}//根据handler在handlerAdapters中获取HandlerAdapterHandlerAdapter 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 {//适配器调用实际的handlermv = 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 afterCompletionmappedHandler.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文件就好。
转载于:https://my.oschina.net/yangjianzhou/blog/3037873
Spring MVC请求处理流程分析相关推荐
- java处理请求的流程_Java Spring mvc请求处理流程详解
Spring mvc请求处理流程详解 前言 spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把.但是感觉讲的都不是很细致,让很多初学者都云里雾里的.本人也是这样,之前研究过, ...
- 初探Spring MVC 请求处理流程
Spring MVC 相对于前面的章节算是比较简单的,我们首先引用<Spring in Action>上的一张图来了解Spring MVC 的核心组件和大致处理流程: 从上图中看到 ①.D ...
- Spring MVC请求处理流程
从web.xml中 servlet的配置开始, 根据servlet拦截的url-parttern,来进行请求转发 Spring MVC工作流程图 图一 图二 Spring工作流程描述 1. 用户向服 ...
- 精尽Spring MVC源码分析 - 一个请求的旅行过程
我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后 ...
- ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
开篇:上一篇我们了解了在WebForm模式下一个Page页面的生命周期,它经历了初始化Init.加载Load以及呈现Render三个重要阶段,其中构造了页面控件树,并对页面控件树进行了大量的递归操作, ...
- java request 处理过程_小猿圈Java开发之从代码看spring mvc请求处理过程
原标题:小猿圈Java开发之从代码看spring mvc请求处理过程 Java作为编程界的常青树,有自己生存的独到之处,小猿圈java讲师今天就分享一个关于从代码看spring mvc请求处理过程,通 ...
- Spring MVC 请求处理过程。你这样回答保证通过面试!
前言 SpringMVC 请求处理相信大家都很熟悉了,本篇主要是基于 SpringMVC 处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题. 关于 Spring MVC 的流程思维 ...
- spring MVC请求处理类注解属性详解
spring MVC请求处理类注解属性详解
- 详述 Spring MVC 启动流程及相关源码分析
文章目录 Web 应用部署初始化过程(Web Application Deployement) Spring MVC 启动过程 Listener 的初始化过程 Filter 的初始化 Servlet ...
最新文章
- VClient 无法连接Vcenter
- 两个例子详解并发编程的可见性问题和有序性问题,通过volatile保证可见性和有序性以及volatile的底层原理——缓存一致性协议MESI和内存屏障禁止指令重排
- Eclipse里web的依赖工程部署的简便方法
- ubuntu - 安装hive
- Memcached简介
- java日期加减秒_Java日期——年、月、日、时、分、秒、周加减计算
- java list有序还是无序_最详细的Java学习点知识脑图,从基础到进阶,看完还有啥你不懂的...
- ajax get请求成功,成功()函数的AJAX GET请求
- Android 系统(260)---Android 读取SIM卡参数
- Gauss Elimination算法分析与实现
- 【POJ1276】【多重背包】凑货币问题
- 安装TeamViewer的监视器驱动后屏幕亮度无法调节
- gnu coreutils4.5.1 hostid.c源码解读
- HTML获取当前IP和当前位置
- HDU - 1873 看病要排队(优先队列)
- SAP ST05 追踪找表法
- Java 获取Word批注所标记的文本和图片
- PyTorch 简介
- 小论文投稿经历与经验
- python m4a转mp3_如何将蜻蜓fm文件转化为音频文件
热门文章
- JavaScript创建对象几种形式
- html track标签,HTML track标签
- java入门申请,《java入门如此简单》——基础知识1
- 若依前后端分离版本如何使用Swagger
- Spring Boot+Ext JS准前后端框架应用的会话(Session)处理
- Weblogic负载均衡/Session复制之集群架构续
- [概率论与数理统计] 常用定义与公式
- 易语言html实现报表打印,易语言报表统计功能例程可打印
- Eclipse install new software 失败 解决方案
- 智能跳过节假日算法java_Quartz 定时任务使用 —— 排除指定节假日时间执行任务(十一)...