详解Ajax与SpringMVC的415错误,到底带不带@ReuqestBody注解?

本文详细分析了在使用Ajax与SpringMVC时遇到的415错误,主要原因是前端发送的Content-Type与后台不匹配。文章通过前后端问题的排查,指出@RequestBody注解的Controller方法只能接收Json字符串,并提供了解决方案——将数据序列化为JSON格式。同时,讨论了在Ajax请求中添加额外属性和去掉@RequestBody注解的影响,以及何时使用@RequestBody注解的考虑因素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

415错误

4xx类错误属于前端的请求错误,通常都是Http报文无法被后台响应,后台判断前端的参数不正确等,而415错误定义为

Unsupported Media Type 服务器无法处理请求附带的媒体格式

错误发生的环境

ajax发起的请求

$.ajax({
    url: "http://localhost:8080/account/",
    type: "POST",
    dataType: 'json',
    data: {
        username: $('#username').val(),
        password: $('#password').val()
    },
    success: function (res) {
        if (res.code == 1) {
            $('#redirect').attr('href', '../catalog/Main.html');
            document.getElementById('redirect').click();
        } else {
            $('#newAccountMessage').text(res.msg);
        }
    },
    error: function (res) {
        $('#newAccountMessage').text(res.msg);
    }
})

后台的Controller的Restful API

当然这和是不是rest没有关系,rest仅仅是一种架构方式和设计规范罢了,在这个场景中是无关的

@PostMapping("")
@ResponseStatus(HttpStatus.CREATED)
public PlatformResult createAccount(@Validated(CreateGroup.class) @RequestBody Account account) {
    if(accountService.getAccount(account.getUsername()) != null) {
        throw new BusinessException("Account is exist");
    }
    account.setFavouriteCategoryId("CATS");
    account.setLanguagePreference("english");
    accountService.insertAccount(account);
    return PlatformResult.success(accountService.getAccount(account.getUsername()));
}

接着来看一下SpringBoot控制台打印的日志和默认返回的封装的错误信息

日志:

2019-06-14 07:20:37.482 WARN 17084 — [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type ‘application/x-www-form-urlencoded;charset=UTF-8’ not supported]

返回的信息:

{
"timestamp":"2019-06-13T23:20:37.490+0000",
"status":415,
"error":"Unsupported Media Type",
"message":"Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
"path":"/account/"
}

是一个json字符串,其中每一个字段的大致意思是这个样子的

  • timestamp 接收请求发生错误的时间戳
  • status http状态码
  • error http状态码对应的简短英文信息
  • message 错误的简短描述
  • path 请求的路径

接着我们再来看一些Chrome显示的抓包
General和Response Headers
Request Headers和提交的数据
注意这里的Content-Type

前端问题确定

就上面的分析来看,问题可以找到了,就是前端请求的数据格式是‘application/x-www-form-urlencoded’,后台不接收这个格式,造成了415错误

那么这里就涉及两点了

  1. 为什么前端发送的数据格式是‘application/x-www-form-urlencoded’,ajax用什么方式来变化数据发送的格式
  2. 后台SpringMVC为什么不接收‘application/x-www-form-urlencoded’这一格式

对于 问题1 我们查看ajax的文档,可以找到两个相关的属性,一个是dataType一个是contentType

  • dataType: 其实dataType在这里并没有关系,dataType是 ‘预期的服务器响应的数据类型’,设置这一值会在http报文的Request Headers的Accept里面体现,权值按高低从左到右排列,如果这一值与后台返回的数据不一致那么ajax可能会抛出解析错误的异常
  • contentType:发送数据到服务器时所使用的内容类型。默认是:“application/x-www-form-urlencoded”。在上述的ajax中我们没有去设置contentType,所以它发送到服务器的数据类型为"application/x-www-form-urlencoded",这和我们的实际情况与分析的是一致的

contentType的常用四种类型为

  1. application/x-www-form-urlencoded
  2. multipart/form-data
  3. application/json
  4. text/xml

想要具体了解的可以查看这篇博文!四种常见的 POST 提交数据方式

那么到这里在分析后端之前,我们可以设置contentType值来尝试一下

如果说直接在上面加入contentType:‘application/json’,结果还是会出错,这一问题我们要到分析后端时才能知晓,就刚才而言我们得到的后台错误信息是

JSON parse error: Unrecognized token ‘username’: was expecting (‘true’, ‘false’ or ‘null’)

可能看着这个会让人感到我的username明明类型是String怎么看着像个布尔变量,当然并不是。这一问题关乎jackson,具体它为何报这样的异常这里就不做讨论了

后端问题确定

这里直接给出结论,被@RquestBody注解的内容只能接收Json字符串

综合分析

‘被@RquestBody注解的内容只能接收Json字符串’加入contentType:'application/json’还是报错这两点我们可以大致确定解决的办法,那就是将ajax发送的数据json序列化之后再发送

使用JSON.stringify(data)来进行json字符串的序列化

const sendData = JSON.stringify({
    username: $('#username').val(),
    password: $('#password').val()
});
$.ajax({
    url: "http://localhost:8080/account/",
    type: "POST",
    dataType: 'json',
    data: {
        username: $('#username').val(),
        password: $('#password').val()
    },
    success: function (res) {
        if (res.code == 1) {
            $('#redirect').attr('href', '../catalog/Main.html');
            document.getElementById('redirect').click();
        } else {
            $('#newAccountMessage').text(res.msg);
        }
    },
    error: function (res) {
        $('#newAccountMessage').text(res.msg);
    }
})

这样子就可以正常通过了

更多的讨论

通过上面的分析和修改,我们的问题已经解决了

但是这还不够

来做一些测试更深入地讨论一些这个问题

1 在ajax提交的数据中加入Account没有的属性
const sendData = JSON.stringify({
    username: $('#username').val(),
    password: $('#password').val(),
    ajax: true
});

比如像代码中的一样,后台没有报错 (我印象中好像时会报错的,是我记错了还是版本更新了…)

不论是request.getParameter()还是@RequestParam都是无法获取到的,它们无法解析json数据

所以如果是使用@RequestBody来接收数据,那么前端应该是与后台的对象一一对应进行传输

2 去掉@ReuqestBody

如果去掉了@ReuqestBody注解,那么SpringMVC将无法解析json数据但可以解析‘application/x-www-form-urlencoded’的数据

比如这个样子

@PostMapping("")
@ResponseStatus(HttpStatus.CREATED)
public PlatformResult createAccount(@Validated(CreateGroup.class) Account account, @RequestParam("ajax") boolean ajax) {
    if(accountService.getAccount(account.getUsername()) != null) {
        throw new BusinessException("Account is exist");
    }
    account.setFavouriteCategoryId("CATS");
    account.setLanguagePreference("english");
    accountService.insertAccount(account);
    return PlatformResult.success(accountService.getAccount(account.getUsername()));
}

前端ajax传输的数据

$.ajax({
    url: "http://localhost:8080/account/",
    type: "POST",
    dataType: 'json',
    data: {
        username: $('#username').val(),
        password: $('#password').val(),
        ajax: true
    },
    success: function (res) {
        if (res.code == 1) {
            $('#redirect').attr('href', '../catalog/Main.html');
            document.getElementById('redirect').click();
        } else {
            $('#newAccountMessage').text(res.msg);
        }
    },
    error: function (res) {
        $('#newAccountMessage').text(res.msg);
    }
})

这种方式有一些优势

  1. 可以附带Account没有的属性,Account具有的属性会自动注入
  2. 对表单也适用
  3. bean validation也同样起作用
到底要不要@ReuqestBody注解

好了,最后回到标题

我们究竟什么时候去使用@ReuqestBody,比较之后我觉得大致是以下几点

  1. 带了@RequestBody的对象与不带的对象,SpringMVC注入的方式是不同的,前者是解析json字符串,由jackson来完成,后者是解析url
  2. @ReuqestBody可以来规约数据,前后端数据交互应该定义好DTO(Data Transfer Object),这样可以更加规范
  3. 在一些客户端只能提交json数据的场景,比如小程序默认就是json(当然可以自己设置contentType)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值