Spring MVC请求原理介绍

 结合最近做过的项目和阅读《Spring 实战》对Spring MVC 请求原理做些粗浅的总结,不对之处请多多指正。

 本文主要介绍两方面的内容

第一,spring MVC请求的流程;

第二,请求完成后Web视图的渲染;

 Spring MVC 请求流程

用户在浏览器中输入一个URL、点击一个连接或者提交一个表单时,请求就开始了。Spring MVC请求会经历多个节点,如下1-1展示请求的节点

 

 

 

 

spring MVC 请求流程

 

(1)请求从浏览器发出时,携带了用户所请求内容的信息,至少包含请求的URL,一般还会带有其他的信息,例如用户的属性信息、提交的表单内容等。

  请求流程的第一个节点是Spring的DispatcherServlet,它是Spring MVC的前端控制器,它是一个单例模式的Servlet,由它将前端的请求委托给应用程序的其他组件来执行实际的处理。一般在web.xml中配置它的初始化

 

(2)DispatcherServlet的任务是将请求发送给Spring MVC 控制器(Controller)。控制器是一个用户处理请求的Spring组件,一般一个应用程序中会有多个控制器。所以DispatchcerServlet需要知道某次请求应该发送给哪个控制器。因此DispatcherServlet会调用一个或多个处理器映射器(handler Mapping)方法来确定请求流程的下一个节点是哪里,处理器映射器会根据请求中携带的URL信息进行决策。

  处理器映射器在应用程序启动时会根据配置、注解完成初始化。

贴出DispatcherServlet#doDispatch源码有助于理解,代码清单1-1:

 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2         HttpServletRequest processedRequest = request;
 3         HandlerExecutionChain mappedHandler = null;
 4         boolean multipartRequestParsed = false;
 5         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 6 
 7         try {
 8             ModelAndView err = null;
 9             Exception dispatchException = null;
10 
11             try {
12                 processedRequest = this.checkMultipart(request);
13                 multipartRequestParsed = processedRequest != request;
14                 mappedHandler = this.getHandler(processedRequest);//取Handler
15                 if(mappedHandler == null || mappedHandler.getHandler() == null) {
16                     this.noHandlerFound(processedRequest, response);
17                     return;
18                 }
19 
20                 HandlerAdapter ex = this.getHandlerAdapter(mappedHandler.getHandler());//合适的适配器
21                 String method = request.getMethod();
22                 boolean isGet = "GET".equals(method);
23                 if(isGet || "HEAD".equals(method)) {
24                     long lastModified = ex.getLastModified(request, mappedHandler.getHandler());
25                     if(this.logger.isDebugEnabled()) {
26                         this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
27                     }
28 
29                     if((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
30                         return;
31                     }
32                 }
33 
34                 if(!mappedHandler.applyPreHandle(processedRequest, response)) {
35                     return;
36                 }
37 
38                 err = ex.handle(processedRequest, response, mappedHandler.getHandler());
39                 if(asyncManager.isConcurrentHandlingStarted()) {
40                     return;
41                 }
42 
43                 this.applyDefaultViewName(request, err);
44                 mappedHandler.applyPostHandle(processedRequest, response, err);
45             } catch (Exception var19) {
46                 dispatchException = var19;
47             }
48 
49             this.processDispatchResult(processedRequest, response, mappedHandler, err, dispatchException);
50         } catch (Exception var20) {
51             this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
52         } catch (Error var21) {
53             this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21);
54         } finally {
55             if(asyncManager.isConcurrentHandlingStarted()) {
56                 if(mappedHandler != null) {
57                     mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
58                 }
59             } else if(multipartRequestParsed) {
60                 this.cleanupMultipart(processedRequest);
61             }
62 
63         }
64 
65     }

 

(3)一但通过映射器找到了合适的控制器之后,会向前端控制器(DispatcherServlet)会将请求发送给找到的控制器,此时DispatcherServlet会卸下负载(即用户请求的信息内容),并耐心的等待控制器处理交付的信息。一般情况,设计良好的控制器只是处理少量的信息甚至不做信息处理的工作,而是将信息建单的封装后交给业务层(service)去处理,此处不做赘述。

(4)控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户,并且可能需要在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用返回原始的信息是不够的(这些信息需要以用户友好的方式进行格式化,一般以HTML方式)。所以,信息需要发送一个视图(View),比如常规的JSP,也可能是htm、表示velocity框架的vm等。

  除了以上工作,控制器最后还要将模型数据打包,标识出用于渲染输出的视图名称。然后它将请求连同模型和视图名称返回给前端控制器DispatcherServlet。

  以上这种方式的好处是控制器不会与特定的视图耦合在一起,处理控制器传给前端控制器(DispatcherServlet)的并不直接绑定特定的视图,它只是传回了视图的逻辑名称,这个逻辑名称用来查找具体的视图实现,这个视图可能是JSP,也可能不是。

(5)前端控制器DispatcherServlet 收到处理控制器返回的模型数据包后,使用视图解析器(view resolve)将视图名称匹配一个特定视图实现页,它可能是JSP,也可能不是。

  具体是什么形式还要看我们使用的视图框架,第二部分再做介绍。

  DispatcherServlet#processDispatchResult 源码清单1-2

 1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
 2         boolean errorView = false;
 3         if(exception != null) {
 4             if(exception instanceof ModelAndViewDefiningException) {
 5                 this.logger.debug("ModelAndViewDefiningException encountered", exception);
 6                 mv = ((ModelAndViewDefiningException)exception).getModelAndView();
 7             } else {
 8                 Object handler = mappedHandler != null?mappedHandler.getHandler():null;
 9                 mv = this.processHandlerException(request, response, handler, exception);
10                 errorView = mv != null;
11             }
12         }
13 
14         if(mv != null && !mv.wasCleared()) {
15             this.render(mv, request, response);
16             if(errorView) {
17                 WebUtils.clearErrorRequestAttributes(request);
18             }
19         } else if(this.logger.isDebugEnabled()) {
20             this.logger.debug("Null ModelAndView returned to DispatcherServlet with name \'" + this.getServletName() + "\': assuming HandlerAdapter completed request handling");
21         }
22 
23         if(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
24             if(mappedHandler != null) {
25                 mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
26             }
27 
28         }
29     }

 

(6)既然DispatcherServlet已经通过模型数据包知道了有哪个视图渲染返回的结果,那么请求任务基本上完成了,它的最后一个节点就是视图的实现。

(7)在输出视图阶段,它交付模型数据,完成请求任务,视图使用模型数据渲染输出,该输出内容通过相应对象(Response)传递给用户客户端。

 

Spring MVC 实现了处理控制器中请求处理逻辑和视图渲染的解耦,这一特性使得请求处理逻辑与视图维护和更新互不影响,只需保证控制器方法和视图的实现在模型内容上达成一致即可,其他方面不应该再有关联。对于模型内容来说视图更关注的是信息展示的布局,而处理控制器更倾向于信息数据。以上讲述了从请求发出到返回模型数据包给视图的过程,那么视图极端是如何对输出数据做渲染的呢?

视图渲染

如果控制器只是通过逻辑视图名称来指向视图的话,那么Spring如何确定使用哪个特定视图来渲染模型数据呢?这就是视图解析器的任务了。

Spring视图解析器的基础类,Spring MVC中定义了名为ViewResolver的接口和View接口来完成视图定位和视图渲染输出工作

ViewResolver 接口代码清单1-3
package org.springframework.web.servlet;

import java.util.Locale;
import org.springframework.web.servlet.View;

public interface ViewResolver {
    View resolveViewName(String var1, Locale var2) throws Exception;
}

当给resolverViewName传入一个视图名称和一个Locale对象时,它会返回一个View实例。

View接口代码清单1-4
package org.springframework.web.servlet;

import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface View {
    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

    String getContentType();

    void render(Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
}

View的工作职责就是接收模型以及Servlet的Request和Response对象,并将输出结果渲染到response对象中,以便输出给用户浏览器。

由此看我们好像还要实现ViewResolver接口和View接口,然而 Spring已经提供了多个内置的解析器的实现,基本适用于绝大多数的场景

以下是Spring自带的12个视图解析器,列表清单1-1

 

视图解析器

描述

BeanNameResolver

将视图解析未Spring上下文中Bean,其中Bean的ID与视图名称相同

ContentNegotiatingViewResolver

通过考虑客户端需要的内容类型来解析视图,委托给另外一个能够产生对应内容类型的视图解析器

FreeMarkerViewResolver      

将视图接卸FreeMarker模板  

InternalResourceViewResolver

将视图解析为Web应用的内部资源(一般为JSP)

JasperReportViewResolver

将视图解析为JasperReport定义

ResourceBundleViewResolver

将视图解析为资源Bundle(一般为属性文件)

TilesViewResolver

将视图解析为Apache Tile定义,其中tile ID与视图名称相同。注意有两个不同的TilesViewResolver实现,分别对应于Tiles 2.0 和Tiles 3.0

UrlBasedViewResolver

直接根据视图的名称解析视图,视图的名称会匹配一个物理视图的定义

VelocityLayoutViewResolver

将视图解析为Velocity布局,从不同Velocity模板中组合页面

VelocityViewResolver

将视图解析为Velocity模板

XMLViewResolver

将视图解析为特定XML 文件中的bean  定义。类似于BeanNameViewResolver

XsltViewResolver

将视图解析为XSLT转换后的结果

 

 

除以上列举的12个视图解析器之外,还有个替代JSP的视图解析技术,就是Thymeleaf。需要单独引入thymeleaf包。

本文只是介绍主要特点,详细使用方式请查阅相关资料(推荐文章:https://blog.csdn.net/u014507083/article/details/56480732)

1.Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
2.Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
3.Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。

 

posted @ 2018-11-04 15:09  zhouzh(水金)  阅读(436)  评论(0)    收藏  举报