SpringMVC數(shù)據(jù)傳輸?shù)倪^(guò)程

這個(gè)主題其實(shí)想了解下SpringMVC的兩種不同的參數(shù)提交方式和接受方式。

SpringMVC的數(shù)據(jù)流程圖:

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

Jsp頁(yè)面提交的參數(shù)信息----》http請(qǐng)求----》DispatcherServlet---》controller(接受方式直接就是對(duì)象)---》View(也是一個(gè)對(duì)象下的屬性)
解答如何將http請(qǐng)求中的參數(shù)直接轉(zhuǎn)變成為對(duì)應(yīng)的對(duì)象的。

jsp數(shù)據(jù)綁定----spring data binder url

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

Servlet中的輸入?yún)?shù)為都是string類(lèi)型,而spring mvc通過(guò)data bind機(jī)制將這些string 類(lèi)型的輸入?yún)?shù)轉(zhuǎn)換為相應(yīng)的command object(根據(jù)view和controller之間傳輸數(shù)據(jù)的具體邏輯,也可稱(chēng)為model attributes, domain model objects)。在這個(gè)轉(zhuǎn)換過(guò)程中,spring實(shí)際是先利用java.beans.PropertyEditor中的 setAdText方法來(lái)把string格式的輸入轉(zhuǎn)換為bean屬性, 亦可通過(guò)繼承java.beans.PropertyEditorSupport來(lái)實(shí)現(xiàn)自定義的PropertyEditors,具體實(shí)現(xiàn)方式可參考spring reference 3.0.5 第 5.4節(jié)中的 Registering additional custom PropertyEditors部分。 自定義完畢propertyEditor后,有以下幾種方式來(lái)注冊(cè)自定義的customer propertyEditor.

WebDataBinder 這個(gè)類(lèi)文件
 * 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}.

對(duì)于requestBody或httpEntity中數(shù)據(jù)的類(lèi)型轉(zhuǎn)換 Spring MVC中對(duì)于requestBody中發(fā)送的數(shù)據(jù)轉(zhuǎn)換不是通過(guò)databind來(lái)實(shí)現(xiàn),而是使用HttpMessageConverter來(lái)實(shí)現(xiàn)具體的類(lèi)型轉(zhuǎn)換。 例如,之前提到的json格式的輸入,在將json格式的輸入轉(zhuǎn)換為具體的model的過(guò)程中,spring mvc首先找出request header中的contenttype,再遍歷當(dāng)前所注冊(cè)的所有的HttpMessageConverter子類(lèi), 根據(jù)子類(lèi)中的canRead()方法來(lái)決定調(diào)用哪個(gè)具體的子類(lèi)來(lái)實(shí)現(xiàn)對(duì)requestBody中的數(shù)據(jù)的解析。如果當(dāng)前所注冊(cè)的httpMessageConverter中都無(wú)法解析對(duì)應(yīng)contexttype類(lèi)型,則拋出HttpMediaTypeNotSupportedException (http 415錯(cuò)誤)。 那么需要如何注冊(cè)自定義的messageConverter呢,很不幸,在spring 3.0.5中如果使用annotation-driven的配置方式的話(huà),無(wú)法實(shí)現(xiàn)自定義的messageConverter的配置,必須老老實(shí)實(shí)的自己定義AnnotationMethodHandlerAdapter的bean定義,再設(shè)置其messageConverters以注冊(cè)自定義的messageConverter。 在3.1版本中,將增加annotation-driven對(duì)自定義的messageConverter的支持 (SPR-7504)。
1 form表單提交的方式可以直接通過(guò)WebDataBinder方式進(jìn)行數(shù)據(jù)轉(zhuǎn)換,將http body中的屬性值注入到對(duì)象中。
下面代碼是我的controller層里接受數(shù)據(jù)的方式: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表單提交,通過(guò)body中的json數(shù)據(jù)提交的方式:

@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();
        }

這里繼續(xù)進(jìn)行分析這兩種方式的不同點(diǎn)以及所用到技術(shù)的方便之處和難點(diǎn),
1 首先第一種方式form表單提交方式:先看一段代碼(這是form表單提交的方式以及在瀏覽器端獲取到的請(qǐng)求信息),可以明顯的看出來(lái)其實(shí)我們的普通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是選擇的當(dāng)前代碼
    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

這里是我的后臺(tái)controller的接收方式,其實(shí)是在SpringMVC的攔截器重進(jìn)行了相應(yīng)的數(shù)據(jù)轉(zhuǎn)換HttpMessageConverters(后面會(huì)單獨(dú)介紹):

@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();
        }   
    }

那么問(wèn)題又來(lái)了,我們這兩種提交的方式中HTTP請(qǐng)求中的form data和request payload的區(qū)別在哪里???

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.

關(guān)鍵就是設(shè)置Content-type這個(gè)Header為application/x-www-form-urlencoded,實(shí)際上對(duì)于常規(guī)的HTML頁(yè)面上的form的Content-type默認(rèn)就是這個(gè)值。
相似的問(wèn)題還發(fā)生在AngularJS的$http方法中,How can I make angular.js post data as form data instead of a request payload? 這個(gè)問(wèn)題竟然有77個(gè)“頂”,看來(lái)遇到此問(wèn)題的人還真不少。
注:這個(gè)問(wèn)題里說(shuō)jQuery的ajax方法是可以的,我今天遇到是不可以的,這個(gè)需要再驗(yàn)證一下。
當(dāng)然解決的方法是一樣的:
$http({      method: 'POST',      url: url,      data: xsrf,      headers: {'Content-Type': 'application/x-www-form-urlencoded'}  })  

解決疑惑的網(wǎng)址:URL

具體的處理辦法就是在我們的ajax請(qǐng)求中
headers: {'Content-Type':'application/x-www-form-urlencoded'}
這里還需要做實(shí)驗(yàn)進(jìn)行測(cè)試才行。
還有一個(gè)問(wèn)題是ajax提交方式和post提交方式有所不同,
用jQuery的ajax方法和post方法分別發(fā)送請(qǐng)求,在后臺(tái)Servlet進(jìn)行處理時(shí)結(jié)果是不一樣的,比如用$.ajax方法發(fā)送請(qǐng)求時(shí)(data參數(shù)是一個(gè)JSON.stringify()處理后的字符串,而不是一個(gè)JSON對(duì)象)但此時(shí)是不可用request.getParam(key) 來(lái)取值的。
如果用$.post方法來(lái)發(fā)送請(qǐng)求(data參數(shù)是一個(gè)JSON對(duì)象,而不要再用JSON.stringify()處理為字符串了),結(jié)果恰恰相反。

@RequestParam

A) 常用來(lái)處理簡(jiǎn)單類(lèi)型的綁定,通過(guò)Request.getParameter() 獲取的String可直接轉(zhuǎn)換為簡(jiǎn)單類(lèi)型的情況( String--> 簡(jiǎn)單類(lèi)型的轉(zhuǎn)換操作由ConversionService配置的轉(zhuǎn)換器來(lái)完成);因?yàn)槭褂胷equest.getParameter()方式獲取參數(shù),所以可以處理get 方式中queryString的值,也可以處理post方式中 body data的值;
B)用來(lái)處理Content-Type: 為 application/x-www-form-urlencoded編碼的內(nèi)容,提交方式GET、POST;
C) 該注解有兩個(gè)屬性: value、required; value用來(lái)指定要傳入值的id名稱(chēng),required用來(lái)指示參數(shù)是否必須綁定;
示例代碼:

@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

該注解常用來(lái)處理Content-Type: 不是application/x-www-form-urlencoded編碼的內(nèi)容,例如application/json, application/xml等;
它是通過(guò)使用HandlerAdapter 配置的HttpMessageConverters來(lái)解析post data body,然后綁定到相應(yīng)的bean上的。
因?yàn)榕渲糜蠪ormHttpMessageConverter,所以也可以用來(lái)處理 application/x-www-form-urlencoded的內(nèi)容,處理完的結(jié)果放在一個(gè)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新增的,它通過(guò)不帶擴(kuò)展名的URL來(lái)訪(fǎng)問(wèn)系統(tǒng)資源。REST是把訪(fǎng)問(wèn)的所有資源看成靜態(tài)的,一個(gè)或一組,每個(gè)不同的URL地址都是一個(gè)靜態(tài)資源。那么Spring MVC 3.0是如何支持REST的呢?簡(jiǎn)單的說(shuō),它是通過(guò)@RequestMapping及@PathVariable注解提供的,在@RequestMapping中指定value與method,就可以處理對(duì)應(yīng)的請(qǐng)求。

SpringMVC如何關(guān)聯(lián)Controller和View的

這里關(guān)注下ViewResolver的組件和View組件之間的關(guān)聯(lián)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>

這個(gè)定義的含義是指UrlBasedViewResolver將使用JstlView對(duì)象來(lái)渲染結(jié)果,并將handler method返回的modelAndView基礎(chǔ)上,
加上目錄前綴/WEB-INF/jsp/和文件名稱(chēng)后綴.jsp。例如結(jié)果返回的viewName為hello world,
則對(duì)應(yīng)的實(shí)際jsp為/WEB-INF/jsp/helloworld.jsp
當(dāng)返回的viewName的前綴為forward:時(shí),spring mvc將結(jié)果通過(guò)forward的方式轉(zhuǎn)到對(duì)應(yīng)的視圖,
例如forward:helloworld。這也是spring mvc缺省的使用模式。

當(dāng)返回的viewName的前綴為redirect:時(shí),spring mvc將結(jié)果通過(guò)redirect的方式轉(zhuǎn)到對(duì)應(yīng)的視圖。
例如redirect:hello world
InternalResourceViewResolver為UrlBasedViewResolver的子類(lèi),它將InternalResourceView作為缺省的View類(lèi),
如果當(dāng)前classpath中有jstl的jar包時(shí)則使用JstlView作為缺省的view來(lái)渲染結(jié)果。
因此以下使用InternalResourceViewResolver的定義應(yīng)該和之前使用UrlBasedViewResolver定義的viewresolver的作用相同。

SpringMVC的邏輯結(jié)構(gòu)圖

springmvc_flow.jpg

131204110311354.jpg

1、Spring MVC的核心是DispatcherServlet,當(dāng)客戶(hù)端發(fā)送一個(gè)請(qǐng)求時(shí),這個(gè)請(qǐng)求經(jīng)過(guò)一系列過(guò)濾器處理。然后DispatcherServlet會(huì)接收到這個(gè)請(qǐng)求。
2、DispatcherServlet會(huì)從HandlerMapping對(duì)象中查找與請(qǐng)求匹配的Controller,并將結(jié)果返回給DispatcherServlet。
3、DispatcherServlet將請(qǐng)求轉(zhuǎn)發(fā)給目標(biāo)Controller,如果定義有攔截器,則會(huì)經(jīng)過(guò)這些攔截器處理。
4、標(biāo)Controller處理完成業(yè)務(wù)邏輯后,會(huì)返回一個(gè)結(jié)果給DispatcherServlet。
5、DispatcherServlet根據(jù)結(jié)果查詢(xún)ViewResolver,找到與之對(duì)應(yīng)的視圖對(duì)象,同樣將結(jié)果返回給DispatcherServlet。
6、DispatcherServlet根據(jù)指定的顯示結(jié)果,調(diào)用模板對(duì)象渲染view。
7、將view返回給客戶(hù)端。
根據(jù)上面的說(shuō)明,可以很很明顯的看出,Spring MVC的核心是Servlet,并且創(chuàng)建的Controller其實(shí)也是一個(gè)Servlet。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容