使用Ajax将@RequestBody中的多个变量传递给Spring MVC控制器


113

是否需要包装支持对象?我想做这个:

@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在查询字符串中使用或@PathVariable与一起使用RequestMethod

Answers:


92

您是正确的,@RequestBody带注释的参数应该可以容纳整个请求并绑定到一个对象,因此您基本上必须使用选项。

如果您绝对想要您的方法,则可以执行一个自定义实现:

说这是您的json:

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

并且您想要将其绑定到此处的两个参数:

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

首先@JsonArg使用JSON路径(如所需信息的路径)定义一个自定义批注say(说):

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

现在编写一个Custom 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中注册即可。有点涉及,但这应该可以正常工作。


2
如何创建自定义注释,请说@JsonArg?
Surendra Jnawali 2014年

为什么是这样?现在我必须在后端创建很多不同的包装器类。我正在将Struts2应用程序迁移到Springboot,在很多情况下,使用ajax发送的JSON对象实际上是模型的两个或多个对象:例如,用户和活动
Jose Ospina

此链接向您显示“如何在Spring MVC中进行注册” geekabyte.blogspot.sg/2014/08/…–
Bodil

3
仍然对为什么不将此选项添加到spring感兴趣。当您有2个long且不愿为其创建包装对象时,这似乎是一个合乎逻辑的选择
tibi

@SurendraJnawali,你可以这样做@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono

88

虽然确实@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为什么不能这样做。与带有requestBody的Map <String,Object>相关的任何风险
Ben Cheng

@Ben我的意思是您可以使用一个单个Map对象在其中存储任意数量的对象,但是顶级对象仍然只能是一个,不能有两个顶级对象。
Jose Ospina '18

1
我认为动态方法的缺点Map<String, String>是:API文档库(swagger / springfox等)可能无法从源代码解析您的请求/响应模式。
stratovarius

10

您可以使用body和path变量混合post参数以获得更简单的数据类型:

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

10

用于传递多个对象,参数,变量等。您可以使用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.
}

希望对您有所帮助。


2

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

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

var1var2是请求参数。

http:/host/form/{params}

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


这不能回答问题,而且是错误的,您不要在POST请求中使用查询字符串
NimChimpsky 2012年

1
@NimChimpsky:当然可以。POST请求仍然可以在URL中包含参数。
马丁·彼得斯

2

一个简单的解决方案是创建一个将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"
}

1
该注释的包装是什么?自动导入仅提供导入jdk.nashorn.internal.objects.annotations.Setter; 编辑。我认为这是Lombok projectlombok.org/features/GetterSetter。如果我错了,请指正我
Gleichmut

@Gleichmut,您可以为变量使用简单的getter和setter方法。它会按您期望的那样工作。
Gimnath

1

除了使用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";
}

希望对您有帮助。


1
那是json,它不起作用。您在方法中指定requestparam,但在ajax发布请求中使用json定义equestbody。
NimChimpsky

请参阅我在ajax调用中没有使用JSON格式。我只使用了两个请求参数,在控制器中,我们可以使用批注@RequestParam获得这些参数。这是工作。我用这个 试一试。
日本Trivedi

我已经尝试过了,这是问题的重点。它不是那样工作的。
NimChimpsky

请具体说明您尝试过的内容。在您的问题中表明这一点。我认为您的要求与我所了解的有所不同。
日本Trivedi

1
第一次尝试就为我工作。谢谢!
Humppakäräjät

1

我调整了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


0

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


0

不知道在哪里添加json,但是如果我用angular这样做,它可以在没有requestBody的情况下工作:angluar:

    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);
}

0

好。我建议创建一个包含所需字段的值对象(Vo)。代码更简单,我们不更改Jackson的功能,甚至更容易理解。问候!


0

您可以使用实现所需的功能@RequestParam。为此,您应该执行以下操作:

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

我知道,这有点骇人听闻,但确实有效!;)


0

您也可以使用@RequestBody Map<String, String> params,然后使用params.get("key")来获取参数的值


0

您也可以使用MultiValue Map来容纳requestBody。这是它的示例。

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

与使用地图保存请求正文时使用@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");
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.