很多人会用 SpringMVC,但对它的处理请求的方式并不清楚,当我们学习一个知识的时候,了解它会让我们更好地使用它,下面我们来看看 SpringMVC 是如何处理请求的。
请求流程的方式
先上图:
Spring MVC 框架也是一个基于请求驱动的 Web 框架,并且使用了前端控制器模式(是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。首先让我们整体看一下 Spring MVC 处理请求的流程:
-
首先用户发送请求,请求被 SpringMVC前端控制器(DispatherServlet)捕获;
-
前端控制器(DispatherServlet)对请求 URL 解析获取请求 URI,根据 URI,调用 HandlerMapping;
-
前端控制器(DispatherServlet)获得返回的 HandlerExecutionChain(包括 Handler 对象以及 Handler 对象对应的拦截器);
-
DispatcherServlet 根据获得的 HandlerExecutionChain,选择一个合适的 HandlerAdapter。(附注:如果成功获得 HandlerAdapter 后,此时将开始执行拦截器的 preHandler(…) 方法);
-
HandlerAdapter 根据请求的 Handler 适配并执行对应的 Handler;HandlerAdapter 提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller)。 在填充 Handler 的入参过程中,根据配置,Spring 将做一些额外的工作:
HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息;
数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等;
数据格式化:如将字符串转换成格式化数字或格式化日期等;
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中);
-
Handler 执行完毕,返回一个 ModelAndView (即模型和视图)给 HandlerAdaptor;
-
HandlerAdaptor 适配器将执行结果 ModelAndView 返回给前端控制器;
-
前端控制器接收到 ModelAndView 后,请求对应的视图解析器;
-
视图解析器解析 ModelAndView 后返回对应 View;
-
渲染视图并返回渲染后的视图给前端控制器;
-
最终前端控制器将渲染后的页面响应给用户或客户端。
案例实操
SpringMVC 请求执行源码解读
对于 SpringMVC 项目所有的请求入口(静态资源除外)这里都是从 web.xml 文件配置的前端控制器 DispatcherServlet 开始:
<!-- servlet请求分发器 -->
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:servlet-context.xml</param-value>
</init-param>
<!-- 表示启动容器时初始化该Servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<!-- 这是拦截请求, /代表拦截所有请求,拦截所有.do请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet UML继承关系图如下:
这里关注蓝线部分继承结构:DispatcherServlet–>FrameworkServlet–>HttpServletBean–>HttpServlet–>GenericServlet–>Servlet,对于请求核心时序图如下:
对于 web 请求的处理,大家都知道是通过继承 HttpServlet 重写其 service 方法,这里打开 DispatcherServlet 源码发现这里并没有看到我们要找的 service 方法,此时到父类 FrameworkServlet 查找如下:可以看到父类重写 HttpServlet service 方法。
FrameworkServlet#service
/**
* Override the parent class implementation in order to intercept PATCH requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
从源码分析来看当请求方法为 patch 请求或者为 null 时执行 processRequest0 方法,其他情况则调用父类 service 方法,大家都知道 SpringMVC 请求大多请求是 get|post 请求为主,此时继续向上查看 FrameworkServlet 父类 HttpServletBean(抽象类继承 HttpServlet 并未重写 service 方法,所以向上继续寻找)–> HttpServlet service 方法:
HttpServlet#service
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);