一般地,出现这种情况很大一部分原因是对ajax中的参数作用不熟悉或者对@RequestBody注解不熟悉。
在jquery的ajax中,如果没加contentType:"application/json",那么data就应该对应的是json对象,反之,如果加了contentType:"application/json",那么ajax发送的就必须是字符串。为什么呢?contentType参数指定的是浏览器将发送什么样类型的编码,比如
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
mage/gif :gif图片
image/jpeg :jpg图片格式
image/png:png图片格式
application/json :json数据格式
application/pdf :pdf格式
application/octet-stream : 二进制流数据
而你不指定contentType则代表为默认的application/x-www-form-urlencoded(表单)类型,这种类型有一个好处,它可以支持很多种情况,并能配合查询字符串(key1=value1&key2=vlaue2)的形式发送到服务器。而且这种默认类型可以很好地配合参数processData(默认为true,可以不用管他),该参数为true的时候,开启自动转化功能,只要是一个合法对象,都能将该对象自动地转化成查询字符串的形式。所以,在contentType默认的情况下(即不写),ajax里的data参数既可以这么传:
$.ajax({
url:"http://xxxxxx",
data: { "username" : "ccc" } , //查询字符串的映射形式(即对象),在默认模式下提交时会自动地转化成查询字符串
.....
});
也可以这么传:
$.ajax({
url:"http://xxxxxx",
data: "username=ccc", //查询字符串形式
.....
});
所以看到这里,差不多也能知道为什么指定了contentType : "application/json"后,data就只能是字符串而且必须是json字符串了,首先你指定json格式,那提交格式肯定与json有关,再一个,因为你指定了"application/json"就意味着你放弃了"application/x-www-form-urlencoded"这种默认模式,默认模式虽可以自动地转化对象,不见得"application/json"这种格式就会自动帮你转,而事实证明,它确实不会帮你自动转。
下面便是两种犯错的例子:
1前台ajax多加了contentType:"application/json",data却错传成json对象:
后台处理:(employee该pojo对象里有username和password等String字段)
结果都为null
将contentType去掉后,
后台成功反射出对象:
2ajax中没加contentType:"application/json",data却直接使用了json字符串(和上面的一个道理)
后台情况:
有人就会说,去掉“application/json” 后的默认模式应该是支持字符串和对象的啊,是的,默认模式确实是可以支持两者,但是要看清楚,默认默认支持的字符串是什么?是json字符串吗?不!是查询字符串。
那么如何在application/json这种非默认模式下让Mvc后台接收到收据呢?这里就需要用到@RequestBody注解了,很巧,这个注解就是专门用来处理非默认模式下的请求的。该注解会提取你传过来的json字符串(注意是json字符串,不是查询字符串),并将提取到的信息绑定到对象中。如果上图加了"application/json"那么controller就应该这么接收:
@RequestMapping("xxx")
......
public String test(@RequestBody Employee employee) {
......
}
3>第三点是真的有点恶心的一点,找了好久才找到。。
那就是 有些 关键的属性在Mvc层中反射失败,会导致其他所有属性都为null
比如上面的joindate对应的pojo是Date,本来input框里的值是 Thu Dec 30 00:00:00 CST 1999 ,传到后台用Date接收,但是显然格式不对,于是Date合成出了错,然后坑爹的就来了,因为这个特殊的属性反射失败,Mvc层就将请求打回去然后导致浏览器报错400 bad request ,从而导致其他所有属性都为null。最后把joindate属性注释掉,后台什么属性的值都收到了。。
希望能够帮到大家!
补充:在"application/json"的时候,ajax请求对跨域支持似乎不好,默认模式下的ajax成功跨域请求一旦改成了"application/json"就会报跨域请求的错
附加部分
关于application/json和application/x-www-form-urlencoded 孰好孰坏问题。
看了很多资料,最主要的看法无非就是application/json能支持解析复杂又嵌套的对象,而认为application/x-www-form-urlencoded在面对复杂、对象+数组嵌套的往往就束手无策了。可事实真是这样吗?application/x-www-form-urlencoded就真的就不能解决复杂+对象嵌套+数组嵌套的情况吗?
//定义字符栈
var stack_chars = [];
//定义[]括号栈
var stack_parentheses = [];
//定义这个下标变量是为了解决 {arr:[1,2,3]} ——> arr[]=1&arr[]=2&arr[]=3 这种情况
//因为 arr[]=1&arr[]=2&arr[]=3 应该变成 arr[0]=1&arr[1]=2&arr[2]=3,所以定义了stackIdx是为了完成括号里下标的递增
var stackIdx = -1;
//判断当前读取的字符是否在[]中
function inParentheses() {
return stack_parentheses.length == 1;
}
//通过调用该方法完成将复杂对象obj转换成springmvc支持嵌套绑定的QueryString
function serialize(obj) {
let jqueryStr = decodeURIComponent(jQuery.param(obj));
let serializeStr = "";
for (let index = 0; index < jqueryStr.length; index++) {
const c = jqueryStr[index];
if (c === "[" || c === "]") {
//入括号栈
stack_parentheses.push(c);
//检查是否栈满, 并进行相关操作
serializeStr += checkCharsStack();
} else {
if (inParentheses()) {
stack_chars.push(c);
} else {
//不在[]中的字符直接拼接到结果串中
serializeStr += c;
}
}
}
return serializeStr;
}
function checkCharsStack() {
//栈满
if (stack_parentheses.length >= 2) {
let c;
let str = "";
let arr = [];
while ((c = stack_chars.pop()) !== undefined) {
//这里把pop出的内容又push进arr是为了防止字符顺序颠倒
arr.push(c);
}
while ((c = arr.pop()) !== undefined) {
str += c;
}
//[]出桟
stack_parentheses.pop();
stack_parentheses.pop();
//如果符号栈为空只能说明JQuery序列化了没有key的数组,即序列化了这种 {arr:[1,2,3]} ——> arr[]=1&arr[]=2&arr[]=3
if(str === ""){
return "["+(++stackIdx)+"]";
}else{
stackIdx = -1;
}
//如果是其他字符,比如arr[1][name]应该转化成arr[1].name
if (isNaN(parseInt(str))) {
return "." + str;
} else {
//如果是数字,比如arr[1],转化后仍是arr[1], 即不需要动他
return "[" + str + "]";
}
}
return "";
}
通过直接调用serialize(obj) 方法便可将复杂又多重嵌套的对象转换成[2*]对应的QueryString,于是,就可以直接提交给后台了,springmvc会自动帮你绑定好一个完整的复杂+嵌套对象。
下面我们来测试一个复杂而又多重嵌套的对象:
后台情况:
浏览器打印后台返回结果:
可以看到,application/x-www-form-urlencoded同样实现了多重嵌套的复杂对象到后台的传递,而且相对于application/json,在后台既不用手动写代码去合成bean对象,也不用手动声明@RequestBody注解,是不是也很方便?
就我个人而言,看了上面的例子后,在非特殊情况下会有些更倾向application/x-www-form-urlencoded了,跨域问题既没有application/json那么多,安全问题也不像application/json那样会有一定概率被一些植入恶意的回调代码,而且能很方便的传输和接受复杂而又多重嵌套的对象。
而且在其他方面,json的数据格式传输时带宽会更小,尤其是在传输数组时,如果数组元素很多,那么application/json传输的数据量就会比application/x-www-form-urlencoded要少很多,自己可以拿上面的代码试试。同时,json的解析速度也会更快。总之,选择用谁个人觉得还是得看自己喜好以及团队里的规范和要求。