การส่งผ่านตัวแปรหลายตัวใน @RequestBody ไปยังตัวควบคุม Spring MVC โดยใช้ Ajax


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ในสตริงแบบสอบถามหรือการใช้งานที่มีทั้ง@PathVariableRequestMethod

คำตอบ:


92

คุณถูกต้องพารามิเตอร์ที่มีคำอธิบายประกอบ @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)

ตอนนี้เขียน 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

ทำไมถึงเป็นแบบนี้? ตอนนี้ฉันต้องสร้างคลาส Wrapper ที่แตกต่างกันมากมายในแบ็กเอนด์ ฉันกำลังย้ายแอปพลิเคชัน Struts2 ไปยัง Springboot และมีหลายกรณีที่ออบเจ็กต์ JSON ที่ส่งโดยใช้ ajax เป็นวัตถุสองชิ้นขึ้นไปของโมเดล: เช่นผู้ใช้และกิจกรรม
Jose Ospina

ลิงก์นี้แสดง "วิธีการลงทะเบียนกับ Spring MVC" geekabyte.blogspot.sg/2014/08/…
Bodil

3
ยังคงสงสัยว่าเหตุใดจึงไม่เพิ่มตัวเลือกนี้ในสปริง ดูเหมือนว่าจะเป็นตัวเลือกเชิงตรรกะเมื่อคุณชอบ 2 longs และอย่าเคยชินที่จะสร้างวัตถุห่อหุ้มสำหรับมัน
tibi

@SurendraJnawali คุณทำได้แบบนี้@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono

88

แม้ว่าจะเป็นความจริงที่@RequestBodyต้องแมปกับวัตถุชิ้นเดียว แต่วัตถุนั้นอาจเป็น a Mapได้ดังนั้นสิ่งนี้จะทำให้คุณได้รับวิธีที่ดีในสิ่งที่คุณพยายามจะบรรลุ (ไม่จำเป็นต้องเขียนวัตถุสำรองไว้):

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

คุณยังสามารถเชื่อมโยงกับObjectNodeของ Jackson ได้หากต้องการต้นไม้ JSON แบบเต็ม:

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

@JoseOspina ทำไมทำไม่ได้ ความเสี่ยงใด ๆ ที่เกี่ยวข้องกับ Map <String, Object> กับ requestBody
Ben Cheng

@ เบ็นฉันหมายความว่าคุณสามารถใช้Mapวัตถุชิ้นเดียวเพื่อเก็บวัตถุจำนวนเท่าใดก็ได้ในนั้น แต่วัตถุระดับบนสุดจะต้องเป็นเพียงชิ้นเดียวไม่มีวัตถุระดับบนสุดสองชิ้น
Jose Ospina

1
ฉันคิดว่าข้อเสียของวิธีการแบบไดนามิกเช่นMap<String, String>ไลบรารีเอกสาร API (swagger / springfox ฯลฯ ) อาจไม่สามารถแยกวิเคราะห์คำขอ / สคีมาการตอบกลับจากซอร์สโค้ดของคุณได้
stratovarius

10

คุณสามารถผสมผสานอาร์กิวเมนต์ของโพสต์ได้โดยใช้ตัวแปร body และ path สำหรับประเภทข้อมูลที่ง่ายขึ้น:

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

10

สำหรับการส่งผ่านวัตถุพารามิเตอร์ตัวแปรและอื่น ๆ คุณสามารถทำได้แบบไดนามิกโดยใช้ 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 GETหรือที่POSTไคลเอ็นต์ส่งมาการแมปคำขอคือส่วนของ URL ซึ่งตัวแปรของ:

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

var1& var2เป็นพารามิเตอร์คำขอ

http:/host/form/{params}

{params}คือการแมปคำขอ คุณสามารถเรียกใช้บริการของคุณชอบhttp:/host/form/userหรือhttp:/host/form/firm ที่ บริษัท Pathvariableและผู้ใช้จะใช้เป็น


สิ่งนี้ไม่ตอบคำถามและผิดคุณไม่ได้ใช้สตริงข้อความค้นหากับคำขอ POST
NimChimpsky

1
@NimChimpsky: แน่ใจนะว่าทำได้ คำขอ POST ยังคงรวมพารามิเตอร์ไว้ใน URL ได้
Martijn Pieters

2

วิธีแก้ปัญหาง่ายๆคือการสร้างคลาส payload ที่มี 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
แพ็คเกจของคำอธิบายประกอบนี้คืออะไร? Autoimport นำเสนอเฉพาะการนำเข้า jdk.nashorn.internal.objects.annotations.Setter; แก้ไข ผมถือว่ามันเป็นลอมบอกprojectlombok.org/features/GetterSetter โปรดแก้ไขฉันหากฉันผิด
Gleichmut

@Gleichmut คุณสามารถใช้ getters และ 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 ใน method แต่กำหนด equestbody ด้วย json ใน ajax post request
NimChimpsky

ดูฉันไม่ได้ใช้รูปแบบ JSON ในการโทร ajax ฉันใช้พารามิเตอร์คำขอสองตัวและในคอนโทรลเลอร์เราสามารถรับพารามิเตอร์เหล่านั้นพร้อมคำอธิบายประกอบ @RequestParam มันกำลังทำงาน ฉันใช้สิ่งนี้ ลองดูสิ
Japan Trivedi

ฉันได้ลองแล้วมันคือจุดของ quesiton มันไม่ได้ผลเช่นนั้น
NimChimpsky

โปรดระบุสิ่งที่คุณได้ลองทำ แสดงว่าในคำถามของคุณ ฉันคิดว่าคุณมีความต้องการที่แตกต่างจากที่ฉันเข้าใจ
Japan 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 สำหรับ Get จะถูกต่อท้ายเป็นสตริงการสืบค้นไปยัง URL แต่สำหรับ POST นั้นจะอยู่ใน Request Body


0

ไม่แน่ใจว่าคุณเพิ่ม json ตรงไหน แต่ถ้าฉันทำแบบนี้ด้วยเชิงมุมมันจะใช้งานได้โดยไม่มี 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

ดี. ฉันขอแนะนำให้สร้าง Value Object (Vo) ที่มีฟิลด์ที่คุณต้องการ รหัสง่ายกว่าเราไม่เปลี่ยนการทำงานของแจ็คสันและเข้าใจง่ายยิ่งขึ้น ความนับถือ!


0

@RequestParamคุณสามารถบรรลุสิ่งที่คุณต้องการโดยใช้ สำหรับสิ่งนี้คุณควรทำสิ่งต่อไปนี้:

  1. ประกาศพารามิเตอร์ RequestParams ที่แสดงถึงอ็อบเจ็กต์ของคุณและตั้งค่าrequiredอ็อพชันเป็นเท็จหากคุณต้องการให้สามารถส่งค่า null ได้
  2. ที่ส่วนหน้าให้สตริงอ็อบเจ็กต์ที่คุณต้องการส่งและรวมไว้เป็นพารามิเตอร์การร้องขอ
  3. ในแบ็กเอนด์ให้เปลี่ยนสตริง JSON กลับเป็นวัตถุที่พวกเขาแสดงโดยใช้ Jackson ObjectMapper หรืออะไรทำนองนั้นและ voila!

ฉันรู้ว่ามันเป็นการแฮ็กเล็กน้อย แต่ได้ผล! ;)


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");
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.