Spring MVC - 为什么不能同时使用 @RequestBody 和 @RequestParam

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19468572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 06:18:13  来源:igfitidea点击:

Spring MVC - Why not able to use @RequestBody and @RequestParam together

springspring-mvcposthttp-posthttp-request-parameters

提问by abhihello123

Using HTTP dev client with Post request and Content-Type application/x-www-form-urlencoded

使用带有 Post 请求和 Content-Type application/x-www-form-urlencoded 的 HTTP 开发客户端

1) Only @RequestBody

1) 只有@RequestBody

Request - localhost:8080/SpringMVC/welcome In Body - name=abc

请求 - localhost:8080/SpringMVC/welcome In Body - name=abc

Code-

代码-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, Model model) {
    model.addAttribute("message", body);
    return "hello";
}

// Gives body as 'name=abc' as expected

// 按预期将 body 设为 'name=abc'

2) Only @RequestParam

2) 只有@RequestParam

Request - localhost:8080/SpringMVC/welcome In Body - name=abc

请求 - localhost:8080/SpringMVC/welcome In Body - name=abc

Code-

代码-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, Model model) {
    model.addAttribute("name", name);
    return "hello";
}

// Gives name as 'abc' as expected

// 按预期将名称命名为“abc”

3) Both together

3)两者一起

Request - localhost:8080/SpringMVC/welcome In Body - name=abc

请求 - localhost:8080/SpringMVC/welcome In Body - name=abc

Code-

代码-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// HTTP Error Code 400 - The request sent by the client was syntactically incorrect.

// HTTP 错误代码 400 - 客户端发送的请求在语法上不正确。

4) Above with params position changed

4) 上面的参数位置改变了

Request - localhost:8080/SpringMVC/welcome In Body - name=abc

请求 - localhost:8080/SpringMVC/welcome In Body - name=abc

Code-

代码-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// No Error. Name is 'abc'. body is empty

// 没有错误。名字是'abc'。身体是空的

5) Together but get type url parameters

5)一起但获取类型url参数

Request - localhost:8080/SpringMVC/welcome?name=xyz In Body - name=abc

请求 - localhost:8080/SpringMVC/welcome?name=xyz In Body - name=abc

Code-

代码-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// name is 'xyz' and body is 'name=abc'

// 名称为“xyz”,主体为“name=abc”

6) Same as 5) but with parameters position changed

6) 同 5) 但参数位置改变

Code -

代码 -

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// name = 'xyz,abc' body is empty

// name = 'xyz,abc' 正文为空

Can someone explain this behaviour?

有人可以解释这种行为吗?

采纳答案by Sotirios Delimanolis

The @RequestBodyjavadoc states

@RequestBodyjavadoc的状态

Annotation indicating a method parameter should be bound to the body of the web request.

指示方法参数的注释应绑定到 Web 请求的正文。

It uses registered instances of HttpMessageConverterto deserialize the request body into an object of the annotated parameter type.

它使用 的注册实例HttpMessageConverter将请求正文反序列化为带注释的参数类型的对象。

And the @RequestParamjavadoc states

@RequestParamjavadoc 状态

Annotation which indicates that a method parameter should be bound to a web request parameter.

指示方法参数应绑定到 Web 请求参数的注释。

  1. Spring binds the body of the request to the parameter annotated with @RequestBody.

  2. Spring binds request parameters from the request body (url-encoded parameters) to your method parameter. Spring will use the name of the parameter, ie. name, to map the parameter.

  3. Parameters are resolved in order. The @RequestBodyis processed first. Spring will consume all the HttpServletRequestInputStream. When it then tries to resolve the @RequestParam, which is by default required, there is no request parameter in the query string or what remains of the request body, ie. nothing. So it fails with 400 because the request can't be correctly handled by the handler method.

  4. The handler for @RequestParamacts first, reading what it can of the HttpServletRequestInputStreamto map the request parameter, ie. the whole query string/url-encoded parameters. It does so and gets the value abcmapped to the parameter name. When the handler for @RequestBodyruns, there's nothing left in the request body, so the argument used is the empty string.

  5. The handler for @RequestBodyreads the body and binds it to the parameter. The handler for @RequestParamcan then get the request parameter from the URL query string.

  6. The handler for @RequestParamreads from both the body and the URL query String. It would usually put them in a Map, but since the parameter is of type String, Spring will serialize the Mapas comma separated values. The handler for @RequestBodythen, again, has nothing left to read from the body.

  1. Spring 将请求的主体绑定到用 注释的参数@RequestBody

  2. Spring 将请求主体中的请求参数(url 编码的参数)绑定到您的方法参数。Spring 将使用参数的名称,即。name, 映射参数。

  3. 参数按顺序解析。的@RequestBody首先处理。Spring 将消耗所有的HttpServletRequestInputStream. 当它尝试解析 时@RequestParam,默认情况下required是 ,查询字符串中没有请求参数或请求正文的剩余部分,即。没有。因此它以 400 失败,因为处理程序方法无法正确处理请求。

  4. 处理程序@RequestParam首先采取行动,读取它可以HttpServletRequestInputStream映射请求参数的内容,即。整个查询字符串/url 编码的参数。它这样做并获取abc映射到参数的值name。当处理程序@RequestBody运行时,请求正文中没有任何内容,因此使用的参数是空字符串。

  5. 用于@RequestBody读取主体并将其绑定到参数的处理程序。然后,处理程序@RequestParam可以从 URL 查询字符串中获取请求参数。

  6. @RequestParam从正文和 URL 查询字符串读取的处理程序。它通常会将它们放在 a 中Map,但由于参数是 type String,Spring 会将它们序列Map化为逗号分隔值。@RequestBody再次,处理程序没有任何东西可以从正文中读取。

回答by Ramesh Karna

It's too late to answer this question, but it could help for new readers, It seems version issues. I ran all these tests with spring 4.1.4 and found that the order of @RequestBodyand @RequestParamdoesn't matter.

回答这个问题为时已晚,但它可以帮助新读者,似乎是版本问题。我跑了所有这些测试弹簧4.1.4和发现的顺序@RequestBody,并@RequestParam没有关系。

  1. same as your result
  2. same as your result
  3. gave body= "name=abc", and name = "abc"
  4. Same as 3.
  5. body ="name=abc", name = "xyz,abc"
  6. same as 5.
  1. 和你的结果一样
  2. 和你的结果一样
  3. 给了body= "name=abc",并且name = "abc"
  4. 与 3 相同。
  5. body ="name=abc", name = "xyz,abc"
  6. 与 5 相同。

回答by 30thh

It happens because of not very straight forward Servlet specification. If you are working with a native HttpServletRequestimplementation you cannot get both the URL encode body and the parameters. Spring does some workarounds, which make it even more strange and nontransparent.

这是因为不是很直接的 Servlet 规范。如果您正在使用本机HttpServletRequest实现,则无法同时获得 URL 编码正文和参数。Spring 做了一些变通方法,这使它变得更加奇怪和不透明。

In such cases Spring (version 3.2.4) re-renders a body for you using data from the getParameterMap()method. It mixes GET and POST parameters and breaks the parameter order. The class, which is responsible for the chaos is ServletServerHttpRequest. Unfortunately it cannot be replaced, but the class StringHttpMessageConvertercan be.

在这种情况下,Spring(版本 3.2.4)使用getParameterMap()方法中的数据为您重新呈现主体。它混合了 GET 和 POST 参数并打破了参数顺序。负责混乱的类是ServletServerHttpRequest。不幸的是它不能被替换,但类StringHttpMessageConverter可以。

The clean solution is unfortunately not simple:

不幸的是,干净的解决方案并不简单:

  1. Replacing StringHttpMessageConverter. Copy/Overwrite the original class adjusting method readInternal().
  2. Wrapping HttpServletRequestoverwriting getInputStream(), getReader()and getParameter*()methods.
  1. 更换StringHttpMessageConverter. 复制/覆盖原来的类调整方法readInternal()
  2. 包裹HttpServletRequest覆盖getInputStream()getReader()getParameter*()方法。

In the method StringHttpMessageConverter#readInternal following code must be used:

在 StringHttpMessageConverter#readInternal 方法中,必须使用以下代码:

    if (inputMessage instanceof ServletServerHttpRequest) {
        ServletServerHttpRequest oo = (ServletServerHttpRequest)inputMessage;
        input = oo.getServletRequest().getInputStream();
    } else {
        input = inputMessage.getBody();
    }

Then the converter must be registered in the context.

然后转换器必须在上下文中注册。

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true/false">
        <bean class="my-new-converter-class"/>
   </mvc:message-converters>
</mvc:annotation-driven>

The step two is described here: Http Servlet request lose params from POST body after read it once

此处描述了第二步:Http Servlet 请求在读取一次后丢失 POST 正文中的参数

回答by Sparticles

You could also just change the @RequestParam default required status to false so that HTTP response status code 400 is not generated. This will allow you to place the Annotations in any order you feel like.

您也可以将 @RequestParam 默认必需状态更改为 false,以便不生成 HTTP 响应状态代码 400。这将允许您按照您喜欢的任何顺序放置注释。

@RequestParam(required = false)String name