这个主题其实想了解下SpringMVC的两种不同的参数提交方式和接受方式。
SpringMVC的数据流程图:
Jsp页面提交的参数信息----》http请求----》DispatcherServlet---》controller(接受方式直接就是对象)---》View(也是一个对象下的属性)
解答如何将http请求中的参数直接转变成为对应的对象的。
jsp数据绑定----spring data binder url
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;
};
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);
}
});
}
这里是我的后台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的逻辑结构图
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。