Truyền nhiều biến trong @RequestBody tới bộ điều khiển Spring MVC bằng Ajax


113

Có cần thiết phải bọc trong một đối tượng hỗ trợ? Tôi muốn làm điều này:

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

Và sử dụng JSON như thế này:

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

Nhưng thay vào đó tôi phải sử dụng:

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

Và sau đó sử dụng JSON này:

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

Đúng không? Tùy chọn khác của tôi sẽ là thay đổi RequestMethodthành GETvà sử dụng @RequestParamtrong chuỗi truy vấn hoặc sử dụng @PathVariablevới một trong hai RequestMethod.

Câu trả lời:


92

Bạn nói đúng, tham số chú thích @RequestBody dự kiến ​​sẽ giữ toàn bộ nội dung của yêu cầu và liên kết với một đối tượng, vì vậy về cơ bản bạn sẽ phải thực hiện với các tùy chọn của mình.

Nếu bạn thực sự muốn cách tiếp cận của mình, có một triển khai tùy chỉnh mà bạn có thể làm:

Nói đây là json của bạn:

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

và bạn muốn liên kết nó với hai tham số ở đây:

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

Trước tiên, hãy xác định một chú thích tùy chỉnh, chẳng hạn @JsonArg, với đường dẫn JSON giống như đường dẫn đến thông tin mà bạn muốn:

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

Bây giờ hãy viết một Custom HandlerMethodArgumentResolver sử dụng JsonPath được định nghĩa ở trên để giải quyết đối số thực tế:

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 "";

    }
}

Bây giờ chỉ cần đăng ký điều này với Spring MVC. Một chút liên quan, nhưng điều này sẽ hoạt động rõ ràng.


2
Làm cách nào để tạo chú thích tùy chỉnh, hãy nói @JsonArg?
Surendra Jnawali

Tại sao thế này? bây giờ tôi phải tạo rất nhiều lớp wrapper khác nhau trong phần phụ trợ. Tôi di chuyển một ứng dụng struts2 để Springboot và nó đã có rất nhiều trường hợp các đối tượng JSON gửi bằng ajax thực sự là hai hoặc nhiều đối tượng của mô hình: ví dụ một người dùng và một Hoạt động
Jose Ospina

liên kết này chỉ cho bạn "cách đăng ký với Spring MVC" geekabyte.blogspot.sg/2014/08/…
Bodil

3
vẫn giao nhau tại sao tùy chọn này không được thêm vào mùa xuân. nó có vẻ như là một lựa chọn hợp lý khi bạn có như 2 chờ đợi và không sẽ không để tạo ra một đối tượng wrapper cho nó
Tibi

@SurendraJnawali bạn có thể làm như thế này@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono

88

Mặc dù đúng là @RequestBodyphải ánh xạ đến một đối tượng duy nhất, nhưng đối tượng đó có thể là một Map, vì vậy, điều này giúp bạn có một cách tốt để đạt được những gì bạn đang cố gắng đạt được (không cần viết tắt một đối tượng sao lưu):

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

Bạn cũng có thể liên kết với ObjectNode của Jackson nếu bạn muốn có một cây JSON đầy đủ:

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

@JoseOspina tại sao không thể làm như vậy. Bất kỳ rủi ro nào liên quan đến Bản đồ <Chuỗi, Đối tượng> với requestBody
Ben Cheng

@Ben Ý tôi là bạn có thể sử dụng MỘT Mapđối tượng duy nhất để lưu trữ bất kỳ số lượng đối tượng nào bên trong nó, nhưng đối tượng cấp cao nhất vẫn phải là một, không thể có hai đối tượng cấp cao nhất.
Jose Ospina,

1
Tôi nghĩ rằng nhược điểm của phương pháp tiếp cận động như Map<String, String>là: các thư viện tài liệu API (swagger / springfox, v.v.) có thể sẽ không thể phân tích cú pháp lược đồ yêu cầu / phản hồi của bạn từ mã nguồn của bạn.
stratovarius

10

Bạn có thể trộn đối số bài đăng bằng cách sử dụng biến body và path cho các kiểu dữ liệu đơn giản hơn:

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

10

Để truyền nhiều đối tượng, tham số, biến, v.v. Bạn có thể thực hiện động bằng cách sử dụng ObjectNode từ thư viện jackson làm tham số của mình. Bạn có thể làm theo cách này:

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

Tôi hy vọng điều này giúp đỡ.


2

@RequestParamlà tham số HTTP GEThoặc POSTdo khách hàng gửi, ánh xạ yêu cầu là một phân đoạn của URL có biến:

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

var1& var2là các thông số yêu cầu.

http:/host/form/{params}

{params}là một ánh xạ yêu cầu. bạn có thể gọi dịch vụ của mình như: http:/host/form/userhoặc http:/host/form/firm nơi công ty & người dùng được sử dụng như Pathvariable.


điều này không trả lời câu hỏi và là sai, bạn không sử dụng một chuỗi truy vấn với các yêu cầu POST
NimChimpsky

1
@NimChimpsky: chắc chắn bạn có thể. Yêu cầu ĐĂNG vẫn có thể bao gồm các thông số trong URL.
Martijn Pieters

2

Giải pháp dễ dàng là tạo một lớp trọng tải có str1 và str2 là các thuộc tính:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

Và sau khi bạn có thể vượt qua

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

và nội dung yêu cầu của bạn là:

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

1
Gói của chú thích này là gì? Autoimport chỉ cung cấp nhập jdk.nashorn.internal.objects.annotations.Setter; BIÊN TẬP. Tôi cho rằng đó là Lombok projectlombok.org/features/GetterSetter . Vui lòng sửa cho tôi nếu tôi sai
Gleichmut

@Gleichmut bạn có thể sử dụng getters và setter đơn giản cho các biến của mình. Nó sẽ hoạt động như bạn mong đợi.
Gimnath

1

Thay vì sử dụng json, bạn có thể làm điều đơn giản.

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

Bây giờ trong bộ điều khiển, bạn cần ánh xạ yêu cầu ajax như dưới đây:

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

Hy vọng điều này sẽ giúp bạn.


1
Đó là json, và nó không hoạt động. Bạn đang chỉ định requestparam trong phương thức, nhưng xác định equestbody với json trong yêu cầu bài đăng ajax.
NimChimpsky

Xem Tôi chưa sử dụng định dạng JSON trong lệnh gọi ajax. Tôi chỉ đơn giản sử dụng hai tham số yêu cầu và trong bộ điều khiển, chúng ta có thể lấy các tham số đó bằng chú thích @RequestParam. Nó đang làm việc. Tôi sử dụng cái này. Chỉ cần cung cấp cho nó một thử.
Japan Trivedi

Tôi đã thử điều đó, đó là điểm của quesiton. Nó không hoạt động như vậy.
NimChimpsky

Vui lòng chỉ định chính xác những gì bạn đã thử. Hãy thể hiện điều đó trong câu hỏi của bạn. Tôi nghĩ rằng bạn có yêu cầu khác với những gì tôi đã hiểu.
Japan Trivedi 16/10/12

1
Làm việc cho tôi trong lần thử đầu tiên. Cảm ơn!
Humppakäräjät

1

Tôi đã điều chỉnh giải pháp của 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;

    }

}

Có gì khác nhau:

  • Tôi đang sử dụng Jackson để chuyển đổi json
  • Tôi không cần giá trị trong chú thích, bạn có thể đọc tên của tham số từ MethodParameter
  • Tôi cũng đọc loại tham số ra khỏi Methodparameter => vì vậy giải pháp nên là chung chung (tôi đã thử nghiệm nó với chuỗi và DTO)

BR


0

tham số yêu cầu tồn tại cho cả GET và POST, Đối với Get, nó sẽ được nối dưới dạng chuỗi truy vấn vào URL nhưng đối với POST, nó nằm trong Request Body


0

Không chắc bạn thêm json vào đâu nhưng nếu tôi làm như thế này với góc thì nó hoạt động mà không cần yêu cầuBody: 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

Tốt. Tôi khuyên bạn nên tạo Đối tượng giá trị (Vo) có chứa các trường bạn cần. Mã đơn giản hơn, chúng tôi không thay đổi hoạt động của Jackson và nó thậm chí còn dễ hiểu hơn. Trân trọng!


0

Bạn có thể đạt được những gì bạn muốn bằng cách sử dụng @RequestParam. Đối với điều này, bạn nên làm như sau:

  1. Khai báo các tham số RequestParams đại diện cho các đối tượng của bạn và đặt required tùy chọn thành false nếu bạn muốn có thể gửi giá trị null.
  2. Trên giao diện người dùng, hãy xâu chuỗi các đối tượng mà bạn muốn gửi và bao gồm chúng dưới dạng tham số yêu cầu.
  3. Trên phần phụ trợ, biến các chuỗi JSON trở lại thành các đối tượng mà chúng đại diện bằng cách sử dụng Jackson ObjectMapper hoặc thứ gì đó tương tự, và thì đấy!

Tôi biết, nó có một chút hack nhưng nó hoạt động! ;)


0

bạn cũng có thể sử dụng @RequestBody Map<String, String> params, sau đó sử dụng params.get("key")để nhận giá trị của tham số


0

Bạn cũng có thể sử dụng Bản đồ đa giá trị để giữ requestBody trong đó. Đây là ví dụ cho nó.

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

không giống như chú thích @RequestBody khi sử dụng Bản đồ để giữ nội dung yêu cầu mà chúng ta cần chú thích bằng @RequestParam

và gửi người dùng trong 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");
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.