स्प्रिंग एमवीसी @ResponseBody पद्धति में HTTP 400 त्रुटि के साथ स्ट्रिंग का जवाब कैसे दें?


389

मैं एक सरल JSON API के लिए स्प्रिंग MVC का उपयोग कर रहा हूं, जिसमें @ResponseBodyनिम्न तरह से आधारित दृष्टिकोण है। (मेरे पास पहले से ही JSON का उत्पादन करने वाला सर्विस लेयर है।)

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        // TODO: how to respond with e.g. 400 "bad request"?
    }
    return json;
}

प्रश्न, दिए गए परिदृश्य में, HTTP 400 त्रुटि के साथ जवाब देने का सबसे सरल, सबसे साफ तरीका क्या है ?

मैं इस तरह से सामने आया:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

... लेकिन मैं इसे यहाँ इस्तेमाल नहीं कर सकता क्योंकि मेरे तरीके का रिटर्न टाइपिंग स्ट्रिंग है, न कि रेस्पोंसेंटिटी।

जवाबों:


624

अपने वापसी प्रकार को बदलें ResponseEntity<>, तो आप नीचे 400 के लिए उपयोग कर सकते हैं

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

और सही अनुरोध के लिए

return new ResponseEntity<>(json,HttpStatus.OK);

अद्यतन १

4.1 वसंत के बाद ResponseEntity में सहायक विधियों का उपयोग किया जा सकता है

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);

तथा

return ResponseEntity.ok(json);

आह, तो आप ResponseEntityभी इस तरह का उपयोग कर सकते हैं । यह अच्छी तरह से काम करता है और मूल कोड के लिए एक सरल बदलाव है - धन्यवाद!
जोनीक

आप किसी भी समय आप भी कस्टम हेडर जोड़ सकते हैं ResponseEntity के सभी निर्माताओं की जाँच स्वागत है
Bassem रेडा Zohdy

7
क्या होगा यदि आप एक स्ट्रिंग वापस के अलावा कुछ और गुजर रहे हैं? एक POJO या अन्य वस्तु के रूप में?
मर्शिकादेंस

11
यह 'ResponseEntity <YourClass>' होगा
बासेम रेडा ज़ोहडी

5
इस दृष्टिकोण का उपयोग करके आपको @ResponseBody एनोटेशन की आवश्यकता नहीं है
Lu55

108

कुछ इस तरह से काम करना चाहिए, मुझे यकीन नहीं है कि एक सरल तरीका है या नहीं:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        response.setStatus( HttpServletResponse.SC_BAD_REQUEST  );
    }
    return json;
}

5
धन्यवाद! यह काम करता है और बहुत सरल भी है। (इस मामले में अप्रयुक्त bodyऔर requestपार्म्स को हटाकर इसे और सरल बनाया जा सकता है ।)
जोनिक

54

जरूरी नहीं कि ऐसा करने का सबसे कॉम्पैक्ट तरीका हो, लेकिन काफी साफ आईएमओ

if(json == null) {
    throw new BadThingException();
}
...

@ExceptionHandler(BadThingException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public @ResponseBody MyError handleException(BadThingException e) {
    return new MyError("That doesnt work");
}

यदि आप स्प्रिंग 3.1+ का उपयोग कर रहे हैं तो संपादित करें आप अपवाद हैंडलर पद्धति में @ResponseBody का उपयोग कर सकते हैं, अन्यथा किसी ModelAndViewचीज़ का उपयोग करें ।

https://jira.springsource.org/browse/SPR-6902


1
क्षमा करें, यह काम नहीं करता है। यह लॉग में लंबे स्टैक ट्रेस के साथ HTTP 500 "सर्वर त्रुटि" पैदा करता है: ERROR org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Failed to invoke @ExceptionHandler method: public controller.TestController$MyError controller.TestController.handleException(controller.TestController$BadThingException) org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representationक्या उत्तर से कुछ गायब है?
जोनीक

इसके अलावा, मैं पूरी तरह से एक और कस्टम प्रकार (MyError) को परिभाषित करने के बिंदु को नहीं समझ पाया। क्या यह जरूरी है? मैं नवीनतम स्प्रिंग (3.2.2) का उपयोग कर रहा हूं।
जोनीक

1
इससे मेरा काम बनता है। मैं javax.validation.ValidationExceptionइसके बजाय का उपयोग करें । (स्प्रिंग 3.1.4)
जेरी चेन

यह उन स्थितियों में काफी उपयोगी है जहां आपकी सेवा और क्लाइंट के बीच एक मध्यवर्ती परत होती है जहां मध्यवर्ती परत की अपनी त्रुटि से निपटने की क्षमता होती है। इस उदाहरण के लिए धन्यवाद @Zutty
स्टॉर्महॉक 15

यह स्वीकृत उत्तर होना चाहिए, क्योंकि यह सामान्य प्रवाह से अपवाद हैंडलिंग कोड को स्थानांतरित करता है और यह HttpServlet *
lilalinux

48

मैं कार्यान्वयन को थोड़ा बदलूंगा:

सबसे पहले, मैं एक UnknownMatchException:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
    public UnknownMatchException(String matchId) {
        super("Unknown match: " + matchId);
    }
}

@ResponseStatus के उपयोग पर ध्यान दें , जो स्प्रिंग द्वारा मान्यता प्राप्त होगा ResponseStatusExceptionResolver। यदि अपवाद को फेंक दिया जाता है, तो यह संबंधित प्रतिक्रिया की स्थिति के साथ प्रतिक्रिया पैदा करेगा। (मैंने उस स्थिति कोड को बदलने की स्वतंत्रता भी ली 404 - Not Foundजिसमें मुझे इस उपयोग के मामले में अधिक उपयुक्त लगता है, लेकिन आप चाहें तो इसे चिपका सकते HttpStatus.BAD_REQUESTहैं।)


अगला, मैं MatchServiceनिम्नलिखित हस्ताक्षर को बदलूंगा:

interface MatchService {
    public Match findMatch(String matchId);
}

अंत में, मैं MappingJackson2HttpMessageConverterJSON क्रमांकन को संभालने के लिए स्प्रिंग को नियंत्रक और प्रतिनिधि को अद्यतन करूँगा (यह डिफ़ॉल्ट रूप से जोड़ा जाता है यदि आप जैक्सन को क्लासपाथ में जोड़ते हैं या @EnableWebMvcया तो या <mvc:annotation-driven />अपने कॉन्फ़िगरेशन में जोड़ते हैं, संदर्भ डॉक्स देखें ):

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Match match(@PathVariable String matchId) {
    // throws an UnknownMatchException if the matchId is not known 
    return matchService.findMatch(matchId);
}

ध्यान दें, डोमेन ऑब्जेक्ट को ऑब्जेक्ट ऑब्जेक्ट या DTO ऑब्जेक्ट से अलग करना बहुत आम है। यह आसानी से एक छोटे डीटीओ कारखाने को जोड़कर प्राप्त किया जा सकता है जो क्रमिक JSON ऑब्जेक्ट लौटाता है:

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MatchDTO match(@PathVariable String matchId) {
    Match match = matchService.findMatch(matchId);
    return MatchDtoFactory.createDTO(match);
}

मेरे पास 500 और मैं लॉग हैं: ay 28, 2015 5:23:31 PM org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver ऑनमेस सेवर: त्रुटि से निपटने के दौरान त्रुटि आई, हार मान लें! org.apache.cxf.interceptor.Fault
razor

सही समाधान, मैं केवल यह जोड़ना चाहता हूं कि मुझे उम्मीद है कि डीटीओ एक रचना है Matchऔर कुछ अन्य वस्तु है।
मार्को सुल्ला

32

यहाँ एक अलग दृष्टिकोण है। निम्नलिखित के समान एक कस्टम Exceptionएनोटेट बनाएं @ResponseStatus

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {

    public NotFoundException() {
    }
}

और जरूरत पड़ने पर फेंक दें।

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new NotFoundException();
    }
    return json;
}

यहां पर स्प्रिंग डॉक्यूमेंटेशन देखें: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exception


यह दृष्टिकोण आपको निष्पादन को समाप्त करने की अनुमति देता है जहां आप स्टैकट्रेस में हैं बिना "विशेष मूल्य" वापस करने के लिए जो कि उस HTTP स्थिति कोड को निर्दिष्ट करना चाहिए जिसे आप वापस करना चाहते हैं।
मुहम्मद गेल्बाना

21

जैसा कि कुछ उत्तरों में उल्लेख किया गया है, प्रत्येक HTTP स्थिति के लिए एक अपवाद वर्ग बनाने की क्षमता है जिसे आप वापस करना चाहते हैं। मुझे प्रत्येक परियोजना के लिए प्रति स्थिति एक वर्ग बनाने का विचार पसंद नहीं है। यहाँ मैं इसके बजाय क्या आया है।

  • एक सामान्य अपवाद बनाएं जो HTTP स्थिति को स्वीकार करता है
  • एक नियंत्रक सलाह अपवाद हैंडलर बनाएँ

कोड के लिए मिलता है

package com.javaninja.cam.exception;

import org.springframework.http.HttpStatus;


/**
 * The exception used to return a status and a message to the calling system.
 * @author norrisshelton
 */
@SuppressWarnings("ClassWithoutNoArgConstructor")
public class ResourceException extends RuntimeException {

    private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * Gets the HTTP status code to be returned to the calling system.
     * @return http status code.  Defaults to HttpStatus.INTERNAL_SERVER_ERROR (500).
     * @see HttpStatus
     */
    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    /**
     * Constructs a new runtime exception with the specified HttpStatus code and detail message.
     * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
     * @param httpStatus the http status.  The detail message is saved for later retrieval by the {@link
     *                   #getHttpStatus()} method.
     * @param message    the detail message. The detail message is saved for later retrieval by the {@link
     *                   #getMessage()} method.
     * @see HttpStatus
     */
    public ResourceException(HttpStatus httpStatus, String message) {
        super(message);
        this.httpStatus = httpStatus;
    }
}

फिर मैं एक नियंत्रक सलाह वर्ग बनाता हूं

package com.javaninja.cam.spring;


import com.javaninja.cam.exception.ResourceException;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;


/**
 * Exception handler advice class for all SpringMVC controllers.
 * @author norrisshelton
 * @see org.springframework.web.bind.annotation.ControllerAdvice
 */
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

    /**
     * Handles ResourceExceptions for the SpringMVC controllers.
     * @param e SpringMVC controller exception.
     * @return http response entity
     * @see ExceptionHandler
     */
    @ExceptionHandler(ResourceException.class)
    public ResponseEntity handleException(ResourceException e) {
        return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
    }
}

इसके प्रयेाग के लिए

throw new ResourceException(HttpStatus.BAD_REQUEST, "My message");

http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/


बहुत अच्छी विधि .. एक साधारण स्ट्रिंग के बजाय मैं एररकोड और संदेश फ़ील्ड के साथ एक जेन्सन को वापस करना पसंद करता हूं ..
Yavuz

1
यह सही उत्तर होना चाहिए, कस्टम स्थिति कोड और संदेश के साथ एक सामान्य और वैश्विक अपवाद हैंडलर: डी
पेड्रो सिल्वा

10

मैं अपने स्प्रिंग बूट एप्लिकेशन में इसका उपयोग कर रहा हूं

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public ResponseEntity<?> match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {

    Product p;
    try {
      p = service.getProduct(request.getProductId());
    } catch(Exception ex) {
       return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
    }

    return new ResponseEntity(p, HttpStatus.OK);
}

9

सबसे आसान तरीका है एक फेंक ResponseStatusException

    @RequestMapping(value = "/matches/{matchId}", produces = "application/json")
    @ResponseBody
    public String match(@PathVariable String matchId, @RequestBody String body) {
        String json = matchService.getMatchJson(matchId);
        if (json == null) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        return json;
    }

3
सर्वश्रेष्ठ उत्तर: रिटर्न प्रकार को बदलने की आवश्यकता नहीं है और अपना अपवाद बनाने की आवश्यकता नहीं है। इसके अलावा, ResponseStatusException यदि आवश्यक हो तो एक कारण संदेश जोड़ने की अनुमति देता है।
मिग

यह नोट करना महत्वपूर्ण है कि ResponseStatusException केवल स्प्रिंग संस्करण 5+
एथन कॉनर

2

स्प्रिंग बूट के साथ, मुझे पूरी तरह से यकीन नहीं है कि यह क्यों आवश्यक था (मुझे इस पर परिभाषित होने के /errorबावजूद भी कमबैक मिला ), लेकिन अपने आप में निम्न काम नहीं किया:@ResponseBody@ExceptionHandler

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
    log.error("Illegal arguments received.", e);
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.code = 400;
    errorMessage.message = e.getMessage();
    return errorMessage;
}

यह अभी भी एक अपवाद है, जाहिरा तौर पर क्योंकि कोई producible मीडिया प्रकार एक अनुरोध विशेषता के रूप में परिभाषित किए गए थे:

// AbstractMessageConverterMethodProcessor
@SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
        ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    Class<?> valueType = getReturnValueType(value, returnType);
    Type declaredType = getGenericType(returnType);
    HttpServletRequest request = inputMessage.getServletRequest();
    List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
    List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (value != null && producibleMediaTypes.isEmpty()) {
        throw new IllegalArgumentException("No converter found for return value of type: " + valueType);   // <-- throws
    }

// ....

@SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
    Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    if (!CollectionUtils.isEmpty(mediaTypes)) {
        return new ArrayList<MediaType>(mediaTypes);

तो मैंने उन्हें जोड़ा।

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
    Set<MediaType> mediaTypes = new HashSet<>();
    mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
    httpServletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    log.error("Illegal arguments received.", e);
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.code = 400;
    errorMessage.message = e.getMessage();
    return errorMessage;
}

और यह मुझे एक "समर्थित संगत मीडिया प्रकार" के माध्यम से मिला, लेकिन तब भी यह काम नहीं करता था, क्योंकि मेरी ErrorMessageगलती थी:

public class ErrorMessage {
    int code;

    String message;
}

जैक्सनमैपर ने इसे "परिवर्तनीय" के रूप में नहीं संभाला, इसलिए मुझे गेटर्स / सेटर्स को जोड़ना पड़ा, और मैंने @JsonPropertyएनोटेशन भी जोड़ा

public class ErrorMessage {
    @JsonProperty("code")
    private int code;

    @JsonProperty("message")
    private String message;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

फिर मुझे अपना संदेश इरादा के अनुसार मिला

{"code":400,"message":"An \"url\" parameter must be defined."}

0

आप बस throw new HttpMessageNotReadableException("error description")स्प्रिंग की डिफ़ॉल्ट त्रुटि हैंडलिंग से भी लाभ उठा सकते हैं ।

हालाँकि, जैसा कि उन डिफ़ॉल्ट त्रुटियों के साथ होता है, कोई प्रतिक्रिया निकाय सेट नहीं किया जाएगा।

मुझे ये उपयोगी लगते हैं जब अनुरोधों को अस्वीकार कर दिया जाता है, जो केवल यथोचित रूप से दस्तकारी कर सकते हैं, संभावित रूप से एक पुरुषवादी इरादे का संकेत देते हैं, क्योंकि वे इस तथ्य को अस्पष्ट करते हैं कि अनुरोध एक गहन, कस्टम सत्यापन और इसके मानदंडों के आधार पर अस्वीकार कर दिया गया था।

एचटीटी, डीटीके


HttpMessageNotReadableException("error description")पदावनत किया गया है।
कुबा Kमोनोवस्की

0

एक और दृष्टिकोण का उपयोग करने के लिए है @ExceptionHandlerके साथ @ControllerAdvice, एक ही कक्षा में अपने सभी संचालकों को केंद्रीकृत करने नहीं तो आप हर नियंत्रक आप एक अपवाद प्रबंधित करना चाहते हैं में हैंडलर तरीकों करना होगा।

आपका हैंडलर वर्ग:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(MyBadRequestException.class)
  public ResponseEntity<MyError> handleException(MyBadRequestException e) {
    return ResponseEntity
        .badRequest()
        .body(new MyError(HttpStatus.BAD_REQUEST, e.getDescription()));
  }
}

आपका कस्टम अपवाद:

public class MyBadRequestException extends RuntimeException {

  private String description;

  public MyBadRequestException(String description) {
    this.description = description;
  }

  public String getDescription() {
    return this.description;
  }
}

अब आप अपने किसी भी नियंत्रक से अपवादों को फेंक सकते हैं, और आप सलाह देने वाले वर्ग के अंदर अन्य संचालकों को परिभाषित कर सकते हैं।


-1

मुझे लगता है कि इस धागे में वास्तव में सबसे आसान, सबसे साफ समाधान है, जो वसंत प्रदान करता है जो JSON मार्शल उपकरण का त्याग नहीं करता है:

https://stackoverflow.com/a/16986372/1278921

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.