SpringMVC数据传输的过程

这个主题其实想了解下SpringMVC的两种不同的参数提交方式和接受方式。

SpringMVC的数据流程图:

c78874dd-1edf-3496-a40b-805fee235db4.png

Jsp页面提交的参数信息----》http请求----》DispatcherServlet---》controller(接受方式直接就是对象)---》View(也是一个对象下的属性)
解答如何将http请求中的参数直接转变成为对应的对象的。

jsp数据绑定----spring data binder url

屏幕快照 2016-04-07 15.50.53.png

Servlet中的输入参数为都是string类型,而spring mvc通过data bind机制将这些string 类型的输入参数转换为相应的command object(根据view和controller之间传输数据的具体逻辑,也可称为model attributes, domain model objects)。在这个转换过程中,spring实际是先利用java.beans.PropertyEditor中的 setAdText方法来把string格式的输入转换为bean属性, 亦可通过继承java.beans.PropertyEditorSupport来实现自定义的PropertyEditors,具体实现方式可参考spring reference 3.0.5 第 5.4节中的 Registering additional custom PropertyEditors部分。 自定义完毕propertyEditor后,有以下几种方式来注册自定义的customer propertyEditor.

WebDataBinder 这个类文件
 * Special {@link DataBinder} for data binding from web request parameters
 * to JavaBean objects. Designed for web environments, but not dependent on
 * the Servlet API; serves as base class for more specific DataBinder variants,
 * such as {@link org.springframework.web.bind.ServletRequestDataBinder}.

对于requestBody或httpEntity中数据的类型转换 Spring MVC中对于requestBody中发送的数据转换不是通过databind来实现,而是使用HttpMessageConverter来实现具体的类型转换。 例如,之前提到的json格式的输入,在将json格式的输入转换为具体的model的过程中,spring mvc首先找出request header中的contenttype,再遍历当前所注册的所有的HttpMessageConverter子类, 根据子类中的canRead()方法来决定调用哪个具体的子类来实现对requestBody中的数据的解析。如果当前所注册的httpMessageConverter中都无法解析对应contexttype类型,则抛出HttpMediaTypeNotSupportedException (http 415错误)。 那么需要如何注册自定义的messageConverter呢,很不幸,在spring 3.0.5中如果使用annotation-driven的配置方式的话,无法实现自定义的messageConverter的配置,必须老老实实的自己定义AnnotationMethodHandlerAdapter的bean定义,再设置其messageConverters以注册自定义的messageConverter。 在3.1版本中,将增加annotation-driven对自定义的messageConverter的支持 (SPR-7504)。
1 form表单提交的方式可以直接通过WebDataBinder方式进行数据转换,将http body中的属性值注入到对象中。
下面代码是我的controller层里接受数据的方式:Object

@RequiresPermissions("sys:role:add")
    @RequestMapping(value = "create", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> create(@Valid SysRole role, Model model) {
        roleService.insert(role);
        return responseSuccessMessage();
    }

2 不在form表单提交,通过body中的json数据提交的方式:

@RequestMapping(value = "updateData", method = RequestMethod.POST,produces="application/json;charset=UTF-8")
    @ResponseBody
    public Map<String, Object> updateData(@RequestBody BerInsuranceTypeEmail rowData,HttpServletRequest request) {
        try {
            berInsuranceTypeEmailService.update(rowData);
            return responseSuccessMessage();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return responseFailMessage();
        }

这里继续进行分析这两种方式的不同点以及所用到技术的方便之处和难点,
1 首先第一种方式form表单提交方式:先看一段代码(这是form表单提交的方式以及在浏览器端获取到的请求信息),可以明显的看出来其实我们的普通form表单提交的方式是
$.post

/**
 * ajax post data
 */
ajaxPostData = function(submitUrl, submitParam, callback, dataType){
    console.log(submitUrl,submitParam,callback,dataType);
    if(dataType==undefined){
        dataType = "json";
    }
    $.post(encodeURI(submitUrl),submitParam, callback,dataType);
    return true;
};
屏幕快照 2016-04-06 09.46.29.png

2 第二种方式:使用ajax的提交方式。

function updateRow(rowIndex, rowData, changes){
    /* rowData是选择的当前代码
    change是改变的的代码 
     console.info(rowData);
    console.info(changes); */
    //alert(JSON.stringify(rowData));
    $.fn.datagrid.extensions.onAfterEdit.apply(this, arguments); 
    $.ajax({
        async:true,
        type:'POST',
        data:JSON.stringify(rowData),
        contentType:'text/json;charset=utf-8',
        url:"${ctx}/a/system/BerTypeEmail/updateData",
        success: function(data){
            $('#dg').datagrid('reload');
            successTipEx(data);
        }
    });
}

屏幕快照 2016-04-06 09.25.55.png

这里是我的后台controller的接收方式,其实是在SpringMVC的拦截器重进行了相应的数据转换HttpMessageConverters(后面会单独介绍):

@RequestMapping(value = "updateData", method = RequestMethod.POST,produces="application/json;charset=UTF-8")
    @ResponseBody
    public Map<String, Object> updateData(@RequestBody BerInsuranceTypeEmail rowData,HttpServletRequest request) {
        try {
            berInsuranceTypeEmailService.update(rowData);
            return responseSuccessMessage();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return responseFailMessage();
        }   
    }

那么问题又来了,我们这两种提交的方式中HTTP请求中的form data和request payload的区别在哪里???

What is the difference between form data and request payload?:
When I send an AJAX Post request and send parameters in queryString in send() method,
Chrome Developer Tool’s XHR capture tool shows the parameters under request payload. 
and when I use jquery’s post function, The tool shows parameters under Form Data section.
What is the difference ?
回答是:
you have not provided enough information how you use the send function, 
but I assume that you do not set mime type to specify you are sending form data
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  
the data sent are in this case encoded as you encode a query string
xhr.send("name=foo&value=bar");  
otherwise it will not be interpreted as form data by Developer Tools.
 jquery does majority of work for you in this regard.

关键就是设置Content-type这个Header为application/x-www-form-urlencoded,实际上对于常规的HTML页面上的form的Content-type默认就是这个值。
相似的问题还发生在AngularJS的$http方法中,How can I make angular.js post data as form data instead of a request payload? 这个问题竟然有77个“顶”,看来遇到此问题的人还真不少。
注:这个问题里说jQuery的ajax方法是可以的,我今天遇到是不可以的,这个需要再验证一下。
当然解决的方法是一样的:
$http({      method: 'POST',      url: url,      data: xsrf,      headers: {'Content-Type': 'application/x-www-form-urlencoded'}  })  

解决疑惑的网址:URL

具体的处理办法就是在我们的ajax请求中
headers: {'Content-Type':'application/x-www-form-urlencoded'}
这里还需要做实验进行测试才行。
还有一个问题是ajax提交方式和post提交方式有所不同,
用jQuery的ajax方法和post方法分别发送请求,在后台Servlet进行处理时结果是不一样的,比如用$.ajax方法发送请求时(data参数是一个JSON.stringify()处理后的字符串,而不是一个JSON对象)但此时是不可用request.getParam(key) 来取值的。
如果用$.post方法来发送请求(data参数是一个JSON对象,而不要再用JSON.stringify()处理为字符串了),结果恰恰相反。

@RequestParam

A) 常用来处理简单类型的绑定,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String--> 简单类型的转换操作由ConversionService配置的转换器来完成);因为使用request.getParameter()方式获取参数,所以可以处理get 方式中queryString的值,也可以处理post方式中 body data的值;
B)用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST;
C) 该注解有两个属性: value、required; value用来指定要传入值的id名称,required用来指示参数是否必须绑定;
示例代码:

@RequiresPermissions("sys:productInfo:edit")
    @RequestMapping(value = "update", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> update(@RequestParam(value = "files") MultipartFile
             files[],String paymentId,HttpServletRequest request,HttpServletResponse response,@Valid BemProductInfo productInfo, Model model) {
        BemProductInfo productInfo1 = new BemProductInfo();
        productInfo1 = productInfoService.getEntityById(productInfo.getId());   
        for (int i = 0; i < files.length; i++) {
                MultipartFile file =files[i];

@RequestBody

该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等;
它是通过使用HandlerAdapter 配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上的。
因为配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api;
示例代码:

@RequestMapping(value = "updateData", method = RequestMethod.POST,produces="application/json;charset=UTF-8")
    @ResponseBody
    public Map<String, Object> updateData(@RequestBody BerInsuranceTypeEmail rowData,HttpServletRequest request) {
        try {
            berInsuranceTypeEmailService.update(rowData);
            return responseSuccessMessage();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return responseFailMessage();
        }   
    }

SpringMVC的特性

REST功能是Spring MVC 3.0新增的,它通过不带扩展名的URL来访问系统资源。REST是把访问的所有资源看成静态的,一个或一组,每个不同的URL地址都是一个静态资源。那么Spring MVC 3.0是如何支持REST的呢?简单的说,它是通过@RequestMapping及@PathVariable注解提供的,在@RequestMapping中指定value与method,就可以处理对应的请求。

SpringMVC如何关联Controller和View的

这里关注下ViewResolver的组件和View组件之间的关联url

<!-- jsp文件配置 -->
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

这个定义的含义是指UrlBasedViewResolver将使用JstlView对象来渲染结果,并将handler method返回的modelAndView基础上,
加上目录前缀/WEB-INF/jsp/和文件名称后缀.jsp。例如结果返回的viewName为hello world,
则对应的实际jsp为/WEB-INF/jsp/helloworld.jsp
当返回的viewName的前缀为forward:时,spring mvc将结果通过forward的方式转到对应的视图,
例如forward:helloworld。这也是spring mvc缺省的使用模式。

当返回的viewName的前缀为redirect:时,spring mvc将结果通过redirect的方式转到对应的视图。
例如redirect:hello world
InternalResourceViewResolver为UrlBasedViewResolver的子类,它将InternalResourceView作为缺省的View类,
如果当前classpath中有jstl的jar包时则使用JstlView作为缺省的view来渲染结果。
因此以下使用InternalResourceViewResolver的定义应该和之前使用UrlBasedViewResolver定义的viewresolver的作用相同。

SpringMVC的逻辑结构图

springmvc_flow.jpg

131204110311354.jpg

1、Spring MVC的核心是DispatcherServlet,当客户端发送一个请求时,这个请求经过一系列过滤器处理。然后DispatcherServlet会接收到这个请求。
2、DispatcherServlet会从HandlerMapping对象中查找与请求匹配的Controller,并将结果返回给DispatcherServlet。
3、DispatcherServlet将请求转发给目标Controller,如果定义有拦截器,则会经过这些拦截器处理。
4、标Controller处理完成业务逻辑后,会返回一个结果给DispatcherServlet。
5、DispatcherServlet根据结果查询ViewResolver,找到与之对应的视图对象,同样将结果返回给DispatcherServlet。
6、DispatcherServlet根据指定的显示结果,调用模板对象渲染view。
7、将view返回给客户端。
根据上面的说明,可以很很明显的看出,Spring MVC的核心是Servlet,并且创建的Controller其实也是一个Servlet。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容