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

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

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;
};

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

這里是我的后臺(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)圖


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。