简体   繁体   English

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

[英]Passing multiple variables in @RequestBody to a Spring MVC controller using Ajax

Is it necessary to wrap in a backing object?是否有必要包裹在支持对象中? I want to do this:我想做这个:

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

And use a JSON like this:并使用这样的 JSON:

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

But instead I have to use:但相反,我必须使用:

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

And then use this JSON:然后使用这个 JSON:

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

Is that correct?那是对的吗? My other option would be to change the RequestMethod to GET and use @RequestParam in query string or use @PathVariable with either RequestMethod .我的另一个选择是将RequestMethod更改为GET并在查询字符串中使用@RequestParam或将@PathVariable与任一RequestMethod一起使用。

You are correct, @RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with your options.你是对的,@RequestBody 注释参数应该包含请求的整个主体并绑定到一个对象,所以你基本上必须选择你的选项。

If you absolutely want your approach, there is a custom implementation that you can do though:如果你绝对想要你的方法,你可以做一个自定义实现:

Say this is your json:说这是你的json:

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

and you want to bind it to the two params here:并且您想将其绑定到此处的两个参数:

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

First define a custom annotation, say @JsonArg , with the JSON path like path to the information that you want:首先定义一个自定义注释,比如@JsonArg ,使用 JSON 路径,比如你想要的信息的路径:

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

Now write a Custom HandlerMethodArgumentResolver which uses the JsonPath defined above to resolve the actual argument:现在编写一个 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 "";

    }
}

Now just register this with Spring MVC.现在只需在 Spring MVC 中注册它。 A bit involved, but this should work cleanly.有点涉及,但这应该可以正常工作。

While it's true that @RequestBody must map to a single object, that object can be a Map , so this gets you a good way to what you are attempting to achieve (no need to write a one off backing object):虽然@RequestBody确实必须映射到单个对象,但该对象可以是Map ,因此这为您提供了一种实现目标的好方法(无需编写单独的支持对象):

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

You can also bind to Jackson's ObjectNode if you want a full JSON tree:如果你想要一个完整的 JSON 树,你也可以绑定到 Jackson 的ObjectNode

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

For passing multiple object, params, variable and so on.用于传递多个对象、参数、变量等。 You can do it dynamically using ObjectNode from jackson library as your param.您可以使用 jackson 库中的 ObjectNode 作为参数动态执行此操作。 You can do it like this way:你可以这样做:

@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.
}

I hope this help.我希望这会有所帮助。

You can mix up the post argument by using body and path variable for simpler data types:您可以通过将 body 和 path 变量用于更简单的数据类型来混合 post 参数:

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

@RequestParam is the HTTP GET or POST parameter sent by client, request mapping is a segment of URL which's variable: @RequestParam是客户端发送的HTTP GETPOST参数,请求映射是一段可变的 URL:

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

var1 & var2 are request params. var1 & var2是请求参数。

http:/host/form/{params}

{params} is a request mapping. {params}是一个请求映射。 you could call your service like : http:/host/form/user or http:/host/form/firm where firm & user are used as Pathvariable .您可以像这样调用您的服务: http:/host/form/userhttp:/host/form/firm其中公司和用户用作Pathvariable

The easy solution is to create a payload class that has the str1 and the str2 as attributes:简单的解决方案是创建一个具有 str1 和 str2 作为属性的负载类:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

And after you can pass在你可以通过之后

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

and the body of your request is:您的请求正文是:

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

Instead of using json, you can do simple thing.您可以做简单的事情,而不是使用 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.>
                });

Now in the controller you need to map the ajax request as below:现在在控制器中,您需要映射 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";
}

Hope this helps you.希望这对你有帮助。

I have adapted the solution of Biju:我已经调整了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;

    }

}

What's the different:有什么不同:

  • I'm using Jackson to convert json我正在使用 Jackson 转换 json
  • I don't need a value in the annotation, you can read the name of the parameter out of the MethodParameter我不需要注解中的值,您可以从 MethodParameter 中读取参数的名称
  • I also read the type of the parameter out of the Methodparameter => so the solution should be generic (i tested it with string and DTOs)我还从 Methodparameter => 中读取了参数的类型,因此解决方案应该是通用的(我使用字符串和 DTO 对其进行了测试)

BR BR

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

You can also use a MultiValue Map to hold the requestBody in. here is the example for it.您还可以使用 MultiValue Map 来保存 requestBody。这是它的示例。

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

unlike the @RequestBody annotation when using a Map to hold the request body we need to annotate with @RequestParam与使用 Map 保存请求正文时的 @RequestBody 注释不同,我们需要使用 @RequestParam 进行注释

and send the user in the Json RequestBody并在 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");
    }

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

Not sure where you add the json but if i do it like this with angular it works without the requestBody: angluar:不确定你在哪里添加 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);
}

Good.好的。 I suggest creating a Value Object (Vo) that contains the fields you need.我建议创建一个包含您需要的字段的值对象 (Vo)。 The code is simpler, we do not change the functioning of Jackson and it is even easier to understand.代码更简单,我们不改变Jackson的功能,更容易理解。 Regards!问候!

You can achieve what you want by using @RequestParam .您可以通过使用@RequestParam来实现您想要的。 For this you should do the following:为此,您应该执行以下操作:

  1. Declare the RequestParams parameters that represent your objects and set the required option to false if you want to be able to send a null value.如果您希望能够发送空值,请声明代表您的对象的 RequestParams 参数并将required选项设置为 false。
  2. On the frontend, stringify the objects that you want to send and include them as request parameters.在前端,对要发送的对象进行字符串化并将它们包含为请求参数。
  3. On the backend turn the JSON strings back into the objects they represent using Jackson ObjectMapper or something like that, and voila!在后端,使用 Jackson ObjectMapper 或类似的东西将 JSON 字符串转换回它们代表的对象,瞧!

I know, its a bit of a hack but it works!我知道,它有点黑客,但它有效! ;) ;)

Use an inner class使用内部类

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

If somebody is interested in the webflux solution, below is a reactive version, based on Biju answer.如果有人对 webflux 解决方案感兴趣,下面是一个基于 Biju 回答的反应式版本。

Please note that there is one very small but synchronized chunk, needed to protect the body from being consumed more than once.请注意,有一个非常小但同步的块,需要保护主体不被多次消耗。 If you prefer a fully non-blocking version, I suggest publishing the flux that obtains json on the same scheduler, to make checking and reading sequential.如果你更喜欢完全非阻塞的版本,我建议在同一个调度器上发布获取 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); } }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM