Spring MVC 后端获取前端提交的json格式字符串并直接转换成control方法对应的参数对象

时间:2022-09-01 17:40:33

场景:

在web应用开发中,spring mvc凭借出现的性能和良好的可扩展性,导致使用日渐增多,成为事实标准,在日常的开发过程中,有一个很常见的场景:即前端通过ajax提交方式,提交参数为一个json对象的字符串,采用application/json的类型,在后端control中利用@RequestBody将json字符串直接转换成对应的Java对象,如:

var dataStr = '[{"id":1476,"name":"test"}]';
$.ajax({
url : '${request.contextPath}/test/jsonParam.json',
data : dataStr,
type : "POST",
async : false,
contentType : "application/json;charset=utf-8", //设置请求头信息
success : function(data) {
console.log(data);
//alert(data);
}
});

在control,我们想直接在处理的方法中获取json字符串对应的对象,如:

@RequestMapping(value = "/jsonParam")
public JSONObject handleJsonParam(WebRequest request, ModelMap model,@RequestBody User user) {
System.out.println(user.getName());
JSONObject jsonObject = JSONObject.parseObject("{\"status\":\"ok\"}");
return jsonObject;
}

在handleJsonParam中,我们希望一进入此方法,参数user对象就已经初始化,而且其对应的id,names属性已经分别被赋值为1476和test,减少json字符串与对象间的反系列化工作,提升开发人员的效率。

场景分析:

我们知道,spring mvc在根据requestmapping找对对应的control方法处理前,会根据请求参数及请求类型做一些数据转换,数据格式化及数据校验等工作,因此我们的解决思路就是在数据转换过程中,将前台请求传过来的json字符串转换成对应的对象,然后将此对象绑定到control方法的参数中。

解决方式:

方式一:最简单的方式,spring mvc为我们提供了一个MappingJackson2HttpMessageConverter类,用于帮助从json字符串转成java的对象,我们只需要在requestmappinghandleradpter中进行配置即可:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJacksonHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="mappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=utf-8</value>
</list>
</property>
</bean>

此方式需要依赖以下三个包:jackson-core(2.4.0),jackson-databind(2.4.0),jackson-annotations(2.4.0)

 方式二:

方式一对所有的json请求都会做如此转换,有时候我们只需要对具体的json字符串做转换或者我们希望控制转换的细节,可以自己创建一个类继承AbstractHttpMessageConverter并实现GenericHttpMessageConverter接口,通过重写canRead,support方法来控制可发序列化的对象类型,并重写read方法实现最终的转换。先看配置文件:

    <mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.web.converter.JsonRequestMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=utf-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

此处我用的是mvc:annotation-driven配置方式,该标签简化了spring的配置,内部会注册默认的DefaultAnnotationHandlerMapping及AnnotationMethodHandlerAdapter示例,与方式一的配置效果类似。其中JsonRequestMessageConverter类如下:

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper; /**
*
* @Description:json请求是对json格式的参数直接进行映射为对应的对象,control中可直接得到对应的对象
* @Create time: 2015年9月21日下午3:47:55
*
*/
public class JsonRequestMessageConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object> {
private final static Charset UTF8 = Charset.forName("UTF-8");
private final static String JSONP_FUNC_NAME = "callback"; private Charset charset = UTF8;
private String jsonpFuncName = JSONP_FUNC_NAME;
private ObjectMapper objectMapper; public JsonRequestMessageConverter() {
super(new MediaType("application", "json", UTF8), new MediaType("application", "*+json", UTF8));
objectMapper = Jackson2ObjectMapperBuilder.json().build();
} public void setJsonpFuncName(String jsonpFuncName) {
this.jsonpFuncName = jsonpFuncName;
} public void setCharset(Charset charset) {
this.charset = charset;
} private HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} private boolean requestJsonp(HttpServletRequest request) {
return request.getRequestURI().endsWith(".jsonp");
} private String getJsonpFunc(HttpServletRequest request) {
String func = request.getParameter(jsonpFuncName);
return StringUtils.isEmpty(func) ? "null" : func;
} /**
* 判断前台请求提交的数据是否可以用此convert读
* type为control中标记为RequestBody的参数类型
* contextClass为对应请求的control类
* mediaType为自持的请求类型,如json、text等
*
*/
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
JavaType javaType = getJavaType(type, contextClass);
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canDeserialize(javaType, causeRef) && canRead(mediaType)) {
return true;
}
Throwable cause = causeRef.get();
if (cause != null) {
String msg = "Failed to evaluate deserialization for type " + javaType;
if (logger.isDebugEnabled()) {
logger.warn(msg, cause);
}
else {
logger.warn(msg + ": " + cause);
}
}
return false;
} private JavaType getJavaType(Type type, Class<?> contextClass) {
return this.objectMapper.getTypeFactory().constructType(type, contextClass);
} /**
*
* @Description:泛型读,将从前台传过来的json请求串映射为具体的参数对象
* @param type
* @param contextClass
* @param inputMessage
* @return
* @throws IOException
* @throws HttpMessageNotReadableException
* @see org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(HttpInputMessage, MethodParameter, Type)
* @see org.springframework.http.converter.GenericHttpMessageConverter#read(java.lang.reflect.Type, java.lang.Class, org.springframework.http.HttpInputMessage)
* @update1:
*
*/
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
return this.objectMapper.readValue(inputMessage.getBody(), this.getJavaType(type, contextClass));
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
}
} /**
*
* @Description:如果泛型读canRead方法返回false,则会调用AbstractHttpMessageConverter中的read方法,此方法会调用readInternal转普通jsonObject
* @param clazz
* @param inputMessage
* @return
* @throws IOException
* @throws HttpMessageNotReadableException
* @see org.springframework.http.converter.AbstractHttpMessageConverter#readInternal(java.lang.Class, org.springframework.http.HttpInputMessage)
* @update1:
*
*/
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
return this.objectMapper.readValue(inputMessage.getBody(), this.getJavaType(clazz, null));
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
}
} /**
*
* @Description:定义此convert支持输出的对象类型,即control端返回的类型,此处支持json格式的字符串及JSONObject/JsonArray对象
* @param clazz
* @return
* @see org.springframework.http.converter.AbstractHttpMessageConverter#supports(java.lang.Class)
* @update1:
*
*/
@Override
protected boolean supports(Class<?> clazz) {
// 只处理control返回的String/JSONObject/JsonArray对象
return String.class.isAssignableFrom(clazz) || JSON.class.isAssignableFrom(clazz);
} /**
*
* @Description:定义此convert可以输出的条件为json格式的字符串及JSONObject/JsonArray对象,且为json请求类型
* @param clazz
* @param mediaType
* @return
* @see org.springframework.http.converter.AbstractHttpMessageConverter#canWrite(java.lang.Class, org.springframework.http.MediaType)
* @update1:
*
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return this.supports(clazz) && canWrite(mediaType);
} /**
*
* @Description:
* @param t
* @param outputMessage
* @throws IOException
* @throws HttpMessageNotWritableException
* @see org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(T, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)
* @see org.springframework.http.converter.AbstractHttpMessageConverter#writeInternal(java.lang.Object, org.springframework.http.HttpOutputMessage)
* @update1:
*
*/
@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// 进来的t值只能是字符串或JSONObject/JsonArray格式
OutputStream out = outputMessage.getBody();
HttpServletRequest request = getRequest();
StringBuilder buffer = new StringBuilder();
boolean requestJsonp = requestJsonp(request);
if (requestJsonp) {
buffer.append(getJsonpFunc(request)).append('(');
}
buffer.append(resolveJsonString(t));
if (requestJsonp) {
buffer.append(");");
}
System.out.println("jsonvonvert:"+buffer);
byte[] bytes = buffer.toString().getBytes(charset);
out.write(bytes);
out.flush();
} private String resolveJsonString(Object t) throws HttpMessageNotWritableException{
if(t instanceof JSON){
return ((JSON)t).toJSONString();
}else if(t instanceof String){
return (String)t;
}
throw new HttpMessageNotReadableException("Not Json Object");
} }

 

 方式三:

通过@InitBinder标签指定json字符串到具体Java对象类型间转换的处理类,这种方式需要了解类细节,不建议使用。

可能出现的问题

有可能会出现如下两个问题:

问题1:control端能得到JsonObject,但是无法泛型到具体的对象,如上面的配置中如果是得到一个List<User>,那么从control方法的参数看到的list内部不是User对象,而是JsonObject;

问题2:有时输入或输出的json数据时会出现乱码。

问题原因:

从前端请求到后端处理,requestMappingHandlerAdapter需要通过requestMapping找到对应的方法,并在方法处理前对参数进行解析,如下图:

Spring MVC 后端获取前端提交的json格式字符串并直接转换成control方法对应的参数对象

在resolverArgument方法体里,会做如下处理:

Spring MVC 后端获取前端提交的json格式字符串并直接转换成control方法对应的参数对象

通过readWithMessageConverters将传过来的json串通过配置的convert类转成具体的对象,如json字符串到jsonObject,在通过binder对象将得到的对象转成泛型,如jsonObject到User对象,因此如果出现问题1,即没有转换成User对象,说明binder处理出现问题,如果出现问题2,说明得到解析得到arg对象时出现了问题,而解析此对象时根据预先配置的convert对象的,说明在转换过程中从request读取数据流出现了问题。

解决方式:

问题一解决:自己的转换类一定要实现实现GenericHttpMessageConverter接口,并在read方法中处理将json字符串到User对象的转换;

问题二解决:每一个converter都需要明确指定支持的MediaType,如:

public JsonRequestMessageConverter() {
super(new MediaType("application", "json", UTF8), new MediaType("application", "*+json", UTF8));
objectMapper = Jackson2ObjectMapperBuilder.json().build();
}

convert默认的字符集的iso-8859,因此如果出现了乱码,需要在配置文件中明确指定字符编码,如:

<bean class="com.letv.shop.demoWeb.web.converter.JsonRequestMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=utf-8</value>
</list>
</property>
</bean>

还有一点需要注意的是:对于上面的配置,如果是通过js发起ajax请求,需要加一行配置:

 <value>application/javascript;charset=UTF-8</value> 
特别是在IE下,因为IE下默认的js请求不会带charset,因此解析用的是默认的iso-8859,需要明确指定编码,如utf-8.
 
spring mvc具有精巧的设计,如果出现了问题,可以通过源码不断的调试进来,细致耐心,则问题自然就可以搞定了。
 

Spring MVC 后端获取前端提交的json格式字符串并直接转换成control方法对应的参数对象的更多相关文章

  1. 如何获取前端提交来得json格式数据

    composer.json { "require": { "guzzlehttp/guzzle": "~6.0" } } composer ...

  2. spring MVC 后端 接收 前端 批量添加的数据(简单示例)

    第一种方式:(使用ajax的方式) 前端代码: <%@ page contentType="text/html;charset=UTF-8" language="j ...

  3. spring mvc 后端获得前端传递过来的参数的方法

    1.通过HttpServletRequest 获得 HttpServletRequest.getParameter(参数名),可以获得form表单中传递的参数,或ajax或url中传递过来的参数,如果 ...

  4. 【转载】spring mvc 后端获得前端传递过来的参数的方法

    1.通过HttpServletRequest 获得 HttpServletRequest.getParameter(参数名),可以获得form表单中传递的参数,或ajax或url中传递过来的参数,如果 ...

  5. Spring MVC无法获取ajax POST的参数和值

    一.怎么会这个样子 很简单的一个想法,ajax以POST的方式提交一个表单,Spring MVC解析.然而一次次的打印null折磨了我整整一天…… 最后的解决现在看来是很明显的问题,“只是当时已惘然” ...

  6. spring mvc 及NUI前端框架学习笔记

    spring mvc 及NUI前端框架学习笔记 页面传值 一.同一页面 直接通过$J.getbyName("id").setValue(id); Set值即可 二.跳转页面(bus ...

  7. spring MVC 如何接收前台传入的JSON对象数组并处理

    spring MVC 如何接收前台传入的JSON对象数组 主要方法: (主要用到的包是 net.sf.json  即:json-lib-2.3-jdk15.jar 完整相关jar包: commons- ...

  8. spring MVC 如何接收前台传入的JSON对象数组

    spring MVC 如何接收前台传入的JSON对象数组 主要方法: (主要用到的包是 net.sf.json  即:json-lib-2.3-jdk15.jar 完整相关jar包: commons- ...

  9. Spring MVC如何获取请求中的参数

    目录 一.获取URL中路径参数 1.1 @PathVariable 注解 1.2 @PathParam 注解 二.获取请求参数: 2.1 GET请求 2.1.1 获取请求中的单个参数:@Request ...

随机推荐

  1. &lbrack;转&rsqb;DBA&comma;SYSDBA&comma;SYSOPER三者的区别

    原文地址:http://www.oracleonlinux.cn/2010/02/dba_sysdba_sysoper/ 什么是DBA?什么是SYSDBA,什么又是SYSOPER?三者究竟有何联系呢? ...

  2. android EditText获取光标位置并安插字符删除字符

    android EditText获取光标位置并插入字符删除字符1.获取光标位置int index = editText.getSelectionStart(); 2.在光标处插入字符int index ...

  3. &lpar;转&rpar;maven设置内存

    Windows环境中 找到文件%M2_HOME%\bin\mvn.bat ,这就是启动Maven的脚本文件,在该文件中你能看到有一行注释为: @REM set MAVEN_OPTS=-Xdebug - ...

  4. Scratch——教小孩子学编码

    教小孩子学编码 http://scratch.mit.edu/ http://v.163.com/movie/2013/3/H/I/M92389L06_M9238GTHI.html

  5. NDK开发小记录 C&plus;&plus;读取java层对象内容

    这是自己NDK开发得小记录,关于C++层读取java层传来的对象内容. 很简单的一个例子, 1.首先在java层定义了一个类NumList: public class NumList { public ...

  6. linux&sol;unix解压缩

    转自:http://blog.sina.com.cn/s/blog_6f2d29af01015ac6.html zip: 压缩: zip [-AcdDfFghjJKlLmoqrSTuvVwXyz$][ ...

  7. openmv扫码通信

    import sensor, image, time import json from pyb import UART sensor.reset() # 初始化相机传感器 sensor.set_pix ...

  8. CALayer动画的暂停&comma;恢复&comma;以及结束时候的回调

    CALayer动画的暂停,恢复,以及结束时候的回调 源码如下: // // ViewController.m // AnimationLineView // // Created by YouXian ...

  9. C&num; DES加密,KEY和IV不同设置的写法

    1.KEY和IV分别赋值 //默认密钥向量 private static byte[] Iv= { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; ...

  10. Spring boot 外部资源配置

    tomcat配置访问图片路径映射到磁盘路径   首先,我在调试页面的时候发现,图片路径为: 1 /webapps/pic_son/img/1234565456.jpg 但是,tomcat中webapp ...