【问题标题】:Passing multiple variables in @RequestBody to a Spring MVC controller using Ajax使用 Ajax 将 @RequestBody 中的多个变量传递给 Spring MVC 控制器
【发布时间】:2012-10-05 07:19:54
【问题描述】:

是否有必要包装在一个支持对象中?我想这样做:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody String str1, @RequestBody String str2) {}

并使用这样的 JSON:

{
    "str1": "test one",
    "str2": "two test"
}

但我必须使用:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody Holder holder) {}

然后使用这个 JSON:

{
    "holder": {
        "str1": "test one",
        "str2": "two test"
    }
}

正确吗?我的另一个选择是将RequestMethod 更改为GET 并在查询字符串中使用@RequestParam 或使用@PathVariableRequestMethod

【问题讨论】:

  • 不要传入 holder 而是将其作为通用 JSONArray 或 JSONObject 传入

标签: java spring http spring-mvc


【解决方案1】:

虽然@RequestBody 确实必须映射到单个对象,但该对象可以是Map,因此这为您提供了一种实现目标的好方法(无需编写一次性支持对象):

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody Map<String, String> json) {
   //json.get("str1") == "test one"
}

如果您想要完整的 JSON 树,也可以绑定到 Jackson 的 ObjectNode

public boolean getTest(@RequestBody ObjectNode json) {
   //json.get("str1").asText() == "test one"

【讨论】:

  • @JoseOspina 为什么不能这样做。与 Map 和 requestBody 相关的任何风险
  • @Ben 我的意思是你可以使用一个Map 对象在其中存储任意数量的对象,但顶级对象仍然必须只有一个,不能有两个顶级对象。
  • 我认为像Map&lt;String, String&gt; 这样的动态 方法的缺点是:API 文档库(swagger/springfox 等)可能无法解析您的请求/响应架构来自您的源代码。
【解决方案2】:

你是对的,@RequestBody 注释参数应该包含整个请求体并绑定到一个对象,所以你基本上必须选择你的选项。

如果您绝对想要您的方法,您可以使用自定义实现:

说这是你的 json:

{
    "str1": "test one",
    "str2": "two test"
}

并且你想将它绑定到这里的两个参数:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)

首先定义一个自定义注释,比如@JsonArg,使用 JSON 路径,如您想要的信息的路径:

public boolean getTest(@JsonArg("/str1") String str1, @JsonArg("/str2") String str2)

现在编写一个自定义HandlerMethodArgumentResolver,它使用上面定义的JsonPath 来解析实际参数:

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.jayway.jsonpath.JsonPath;

public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{

    private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonArg.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String body = getRequestBody(webRequest);
        String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
        return val;
    }

    private String getRequestBody(NativeWebRequest webRequest){
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
        if (jsonBody==null){
            try {
                String body = IOUtils.toString(servletRequest.getInputStream());
                servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
                return body;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return "";

    }
}

现在只需使用 Spring MVC 注册它。有点涉及,但这应该可以正常工作。

【讨论】:

  • 如何创建自定义注释,请说@JsonArg?
  • 这是为什么?现在我必须在后端创建很多不同的包装类。我正在将 Struts2 应用程序迁移到 Springboot,并且在很多情况下,使用 ajax 发送的 JSON 对象实际上是模型的两个或多个对象:例如一个用户和一个活动
  • 这个链接告诉你“如何用 Spring MVC 注册这个”geekabyte.blogspot.sg/2014/08/…
  • 仍然很有趣为什么这个选项没有被添加到 spring。当您有 2 个 long 并且不会为其创建包装器对象时,这似乎是一个合乎逻辑的选择
  • @SurendraJnawali 你可以这样做@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
【解决方案3】:

用于传递多个对象、参数、变量等。您可以使用 jackson 库中的 ObjectNode 作为参数动态地执行此操作。你可以这样做:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody ObjectNode objectNode) {
   // And then you can call parameters from objectNode
   String strOne = objectNode.get("str1").asText();
   String strTwo = objectNode.get("str2").asText();

   // When you using ObjectNode, you can pas other data such as:
   // instance object, array list, nested object, etc.
}

希望对你有所帮助。

【讨论】:

    【解决方案4】:

    对于更简单的数据类型,您可以通过使用 body 和 path 变量来混合 post 参数:

    @RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
        public ResponseEntity<List<String>> newTrade(@RequestBody Trade trade, @PathVariable long portfolioId) {
    ...
    }
    

    【讨论】:

      【解决方案5】:

      @RequestParam是客户端发送的HTTP GETPOST参数,请求映射是一段可变的URL:

      http:/host/form_edit?param1=val1&param2=val2
      

      var1 & var2 是请求参数。

      http:/host/form/{params}
      

      {params} 是一个请求映射。您可以致电您的服务:http:/host/form/userhttp:/host/form/firm 其中公司和用户用作Pathvariable

      【讨论】:

      • 这没有回答问题并且是错误的,您没有使用带有 POST 请求的查询字符串
      • @NimChimpsky:当然可以。 POST 请求仍然可以在 URL 中包含参数。
      【解决方案6】:

      简单的解决方案是创建一个具有 str1 和 str2 作为属性的有效负载类:

      @Getter
      @Setter
      public class ObjHolder{
      
      String str1;
      String str2;
      
      }
      

      然后就可以通过了

      @RequestMapping(value = "/Test", method = RequestMethod.POST)
      @ResponseBody
      public boolean getTest(@RequestBody ObjHolder Str) {}
      

      您的请求正文是:

      {
          "str1": "test one",
          "str2": "two test"
      }
      

      【讨论】:

      • 这个注解的包是什么?自动导入仅提供 import jdk.nashorn.internal.objects.annotations.Setter;编辑。我假设它是龙目岛projectlombok.org/features/GetterSetter。如果我错了,请纠正我
      • @Gleichmut 您可以对变量使用简单的 getter 和 setter。它会按您的预期工作。
      【解决方案7】:

      你可以做一些简单的事情,而不是使用 json。

      $.post("${pageContext.servletContext.contextPath}/Test",
                      {
                      "str1": "test one",
                      "str2": "two test",
      
                              <other form data>
                      },
                      function(j)
                      {
                              <j is the string you will return from the controller function.>
                      });
      

      现在您需要在控制器中映射 ajax 请求,如下所示:

       @RequestMapping(value="/Test", method=RequestMethod.POST)
          @ResponseBody
          public String calculateTestData(@RequestParam("str1") String str1, @RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
                  <perform the task here and return the String result.>
      
                  return "xyz";
      }
      

      希望对你有所帮助。

      【讨论】:

      • 那是json,它不起作用。您在方法中指定 requestparam,但在 ajax 发布请求中使用 json 定义 equestbody。
      • 请参阅我没有在 ajax 调用中使用 JSON 格式。我只使用了两个请求参数,在控制器中我们可以通过注释@RequestParam 获取这些参数。这是工作。我用这个。试试看吧。
      • 我已经尝试过了,这是问题的重点。它不是那样工作的。
      • 请具体说明您尝试过的内容。在你的问题中表明这一点。我认为您的要求与我所理解的不同。
      • 第一次尝试就为我工作。谢谢!
      【解决方案8】:

      我已经改编了Biju的解决方案:

      import java.io.IOException;
      
      import javax.servlet.http.HttpServletRequest;
      
      import org.apache.commons.io.IOUtils;
      import org.springframework.core.MethodParameter;
      import org.springframework.web.bind.support.WebDataBinderFactory;
      import org.springframework.web.context.request.NativeWebRequest;
      import org.springframework.web.method.support.HandlerMethodArgumentResolver;
      import org.springframework.web.method.support.ModelAndViewContainer;
      
      import com.fasterxml.jackson.databind.JsonNode;
      import com.fasterxml.jackson.databind.ObjectMapper;
      
      
      public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
      
          private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
      
          private ObjectMapper om = new ObjectMapper();
      
          @Override
          public boolean supportsParameter(MethodParameter parameter) {
              return parameter.hasParameterAnnotation(JsonArg.class);
          }
      
          @Override
          public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
              String jsonBody = getRequestBody(webRequest);
      
              JsonNode rootNode = om.readTree(jsonBody);
              JsonNode node = rootNode.path(parameter.getParameterName());    
      
              return om.readValue(node.toString(), parameter.getParameterType());
          }
      
      
          private String getRequestBody(NativeWebRequest webRequest){
              HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
      
              String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
              if (jsonBody==null){
                  try {
                      jsonBody = IOUtils.toString(servletRequest.getInputStream());
                      webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
                  } catch (IOException e) {
                      throw new RuntimeException(e);
                  }
              }
              return jsonBody;
      
          }
      
      }
      

      有什么不同:

      • 我正在使用Jackson转换json
      • 我不需要注解中的值,你可以读取的名称 MethodParameter 中的参数
      • 我还从 Methodparameter 中读取了参数的类型 => 所以解决方案应该是通用的(我使用字符串和 DTO 对其进行了测试)

      BR

      【讨论】:

        【解决方案9】:

        您还可以使用 MultiValue Map 来保存 requestBody。 这是它的例子。

            foosId -> pathVariable
            user -> extracted from the Map of request Body 
        

        与使用 Map 保存请求正文时的 @RequestBody 注释不同,我们需要使用 @RequestParam 进行注释

        并在 Json RequestBody 中发送用户

          @RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
                    + "/json",
                    consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
                    produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
            @ResponseBody
            public String postFoos(@PathVariable final Map<String, String> pathParam,
                    @RequestParam final MultiValueMap<String, String> requestBody) {
                return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
            }
        

        【讨论】:

          【解决方案10】:

          GET 和 POST 都存在请求参数,对于 Get,它将作为查询字符串附加到 URL,但对于 POST,它位于请求正文中

          【讨论】:

            【解决方案11】:

            不确定你在哪里添加 json,但如果我用 angular 这样做,它可以在没有 requestBody 的情况下工作: 角度:

                const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
                return this.http.post<any>( this.urlMatch,  params , { observe: 'response' } );
            

            java:

            @PostMapping(URL_MATCH)
            public ResponseEntity<Void> match(Long str1, Long str2) {
              log.debug("found: {} and {}", str1, str2);
            }
            

            【讨论】:

              【解决方案12】:

              很好。 我建议创建一个包含您需要的字段的值对象 (Vo)。代码更简单,我们不改变杰克逊的功能,更容易理解。 问候!

              【讨论】:

                【解决方案13】:

                您可以使用@RequestParam 实现您想要的。为此,您应该执行以下操作:

                1. 声明代表对象的 RequestParams 参数并将required 选项设置为 false,如果您希望能够发送空值。
                2. 在前端,对要发送的对象进行字符串化,并将它们作为请求参数包含在内。
                3. 在后端,使用 Jackson ObjectMapper 或类似工具将 JSON 字符串转换回它们所代表的对象,瞧!

                我知道,这有点小技巧,但它确实有效! ;)

                【讨论】:

                  【解决方案14】:

                  你也可以使用@RequestBody Map&lt;String, String&gt; params,然后使用params.get("key")获取参数值

                  【讨论】:

                    【解决方案15】:

                    使用内部类

                    @RestController
                    public class MyController {
                    
                        @PutMapping("/do-thing")
                        public void updateFindings(@RequestBody Bodies.DoThing body) {
                            ...
                        }
                    
                    
                        private static class Bodies {
                            public static class DoThing {
                                public String name;
                                public List<String> listOfThings;
                            }
                        }
                    }
                    

                    【讨论】:

                      【解决方案16】:

                      如果有人对 webflux 解决方案感兴趣,下面是基于 Biju 回答的响应式版本。

                      请注意,有一个非常小但同步的块,需要保护身体不被多次消耗。如果你更喜欢完全非阻塞的版本,我建议在同一个调度器上发布获取 json 的通量,以使检查和读取顺序。

                      import com.fasterxml.jackson.core.JsonProcessingException;
                      import com.fasterxml.jackson.databind.JsonNode;
                      import com.fasterxml.jackson.databind.ObjectMapper;
                      import lombok.RequiredArgsConstructor;
                      import lombok.extern.slf4j.Slf4j;
                      import org.springframework.core.MethodParameter;
                      import org.springframework.core.io.buffer.DataBuffer;
                      import org.springframework.core.io.buffer.DataBufferUtils;
                      import org.springframework.web.reactive.BindingContext;
                      import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
                      import org.springframework.web.server.ServerWebExchange;
                      import reactor.core.publisher.Mono;
                      
                      import java.nio.charset.StandardCharsets;
                      
                      @Slf4j
                      @RequiredArgsConstructor
                      public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
                          private static final String ATTRIBUTE_KEY = "BODY_TOSTRING_RESOLVER";
                          private final ObjectMapper objectMapper;
                      
                      
                          @Override
                          public boolean supportsParameter(MethodParameter parameter) {
                              return parameter.hasParameterAnnotation(JsonArgument.class);
                          }
                      
                      
                          @Override
                          public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
                                                              ServerWebExchange exchange) {
                              String fieldName = parameter.getParameterName();
                              Class<?> clz = parameter.getParameterType();
                      
                              return getRequestBody(exchange).map(body -> {
                                  try {
                                      JsonNode jsonNode = objectMapper.readTree(body).get(fieldName);
                                      String s = jsonNode.toString();
                                      return objectMapper.readValue(s, clz);
                                  } catch (JsonProcessingException e) {
                                      log.error(e.getMessage(), e);
                                      throw new RuntimeException(e);
                                  }
                              });
                          }
                      
                      
                          private Mono<String> getRequestBody(ServerWebExchange exchange) {
                              Mono<String> bodyReceiver;
                              synchronized (exchange) {
                                  bodyReceiver = exchange.getAttribute(ATTRIBUTE_KEY);
                                  if (bodyReceiver == null) {
                                      bodyReceiver = exchange.getRequest().getBody()
                                              .map(this::convertToString)
                                              .single()
                                              .cache();
                                      exchange.getAttributes().put(ATTRIBUTE_KEY, bodyReceiver);
                                  }
                              }
                              return bodyReceiver;
                          }
                      
                          private String convertToString(DataBuffer dataBuffer) {
                              byte[] bytes = new byte[dataBuffer.readableByteCount()];
                              dataBuffer.read(bytes);
                              DataBufferUtils.release(dataBuffer);
                              return new String(bytes, StandardCharsets.UTF_8);
                          }
                      }

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2022-01-11
                        相关资源
                        最近更新 更多