如何在Spring Webflux 中使用@RequestBody 并避免IllegalStateException?

How to use @RequestBody in Spring Webflux and avoid IllegalStateException?

我是 Webflux 的新手,一般来说 Spring 我在设置一个处理 POST 请求的简单服务器时遇到了麻烦:

WebfluxtestApplication.java

package com.test.webfluxtest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@SpringBootApplication
@RestController
public class WebfluxtestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebfluxtestApplication.class, args);
    }

    @PostMapping
    public Mono<Person> processPerson(@RequestBody Mono<Person> personMono){
        Person person = personMono.block();
        person.setAge(42);
        return Mono.just(person);
    }

}

其中 Person 如下:

Person.java

package com.test.webfluxtest;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {

  private String name;
  private int age;

  public Person(@JsonProperty String name, @JsonProperty int age){
    this.name = name;
    this.age = age;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return this.name + "  " + this.age;
  }
}

但是,当我发送 POST 请求时,我收到 java.lang.IllegalStateException。更具体地说,这里是堆栈跟踪的顶部:

java.lang.IllegalStateException: In a WebFlux application, form data is accessed via ServerWebExchange.getFormData().
    at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:158) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ? HTTP POST "/" [ExceptionHandlingWebHandler]
Stack trace:
        at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:158) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:126) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.annotation.RequestBodyMethodArgumentResolver.resolveArgument(RequestBodyMethodArgumentResolver.java:64) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:123) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:200) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:137) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.lambda$handle(RequestMappingHandlerAdapter.java:200) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) ~[reactor-core-3.3.0.RELEASE.jar:3.3.0.RELEASE]

问题似乎是我直接使用了 @RequestBody 标签,而不是通过 ServerWebExchange 访问表单数据。但是,我在网上看到了多个示例,其中 @RequestBody 与 WebFlux 一起使用没有任何问题。例如 the framework documentation of Spring itself.

使用标签生成 IllegalStateExceptions(如 here)似乎也存在类似问题,但异常消息不同。

我使用 spring-boot 初始值设定项来创建项目的成绩文件,但我自己没有更改它。

有没有人body知道问题是什么以及我该如何解决?

编辑

多亏了 Aviad Levy,我才得以找到问题所在。 也就是说,POST 请求的格式不正确,因为 body 是在 url 中编码发送的。为了让它工作,我必须确保请求 body 是 JSON object 并且 Content-Type header 设置为 application/json。 我仍然对为什么使用错误的格式会引发该特定消息的异常感到困惑。

更新:这个答案假设你想post形成数据,这是基于错误信息的假设。

简短的回答是使用 @ModelAttribute。此外,您不能阻塞,而不是不切换线程(例如通过 publishOn() 运算符),但在这里您不必那样做。这有效:

@PostMapping("/test")
public Mono<Person> processPerson(@ModelAttribute Mono<Person> personMono){
    return personMono.doOnNext(p -> p.setAge(42));
}

长答案是 @RequestBody 仅支持 MultiValueMap<String,String> 表单数据。要将表单数据绑定到对象上,您需要使用 @ModelAttribute。但是,在这种情况下,您看到的错误消息是关于不同内容的。它试图防止主体被消耗两次,一次是通过 exchange.getFormData() 访问表单参数的代码,第二次是通过声明控制器方法参数。因此错误消息仍然适用,但前提是您尝试绑定到 MultiValueMap.

还有改进的余地。至少,可以改进错误消息。此外,@RequestBody 可以改进对表单数据的支持,因为显然您尝试做的事情是可能的,并且您不必记住切换注释。

您介意在 https://github.com/spring-projects/spring-framework/issues 中创建一个问题吗?