Làm cách nào để trả lời lỗi HTTP 400 trong phương thức Spring MVC @ResponseBody trả về Chuỗi?


389

Tôi đang sử dụng Spring MVC cho API JSON đơn giản, với @ResponseBodycách tiếp cận dựa trên như sau. (Tôi đã có một lớp dịch vụ sản xuất JSON trực tiếp.)

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

Câu hỏi là, trong kịch bản đã cho, cách đơn giản nhất, sạch nhất để trả lời với lỗi HTTP 400 là gì?

Tôi đã đi qua các phương pháp như:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

... nhưng tôi không thể sử dụng nó ở đây vì kiểu trả về của phương thức của tôi là String, không phải là FeedbackEntity.

Câu trả lời:


624

thay đổi loại trả lại của bạn thành ResponseEntity<>, sau đó bạn có thể sử dụng dưới đây cho 400

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

và cho yêu cầu chính xác

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

CẬP NHẬT 1

Sau mùa xuân 4.1, các phương thức trợ giúp trong FeedbackEntity có thể được sử dụng như

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

return ResponseEntity.ok(json);

Ah, vì vậy bạn cũng có thể sử dụng ResponseEntitynhư thế này. Điều này hoạt động độc đáo và chỉ là một thay đổi đơn giản cho mã gốc cảm ơn!
Jonik

bạn được chào đón bất cứ lúc nào bạn có thể thêm tiêu đề tùy chỉnh, kiểm tra tất cả các nhà xây dựng của
FeedbackEntity

7
Điều gì nếu bạn đang vượt qua một cái gì đó ngoài một chuỗi trở lại? Như trong một POJO hoặc đối tượng khác?
mrshickadance

11
đó sẽ là 'FeedbackEntity <YourClass>'
Bassem Reda Zohdy

5
Sử dụng phương pháp này, bạn không cần chú thích @ResponseBody nữa
Lu55

108

Một cái gì đó như thế này sẽ hoạt động, tôi không chắc có hay không có một cách đơn giản hơn:

@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
Cảm ơn! Điều này hoạt động và cũng khá đơn giản. (Trong trường hợp này, nó có thể được đơn giản hóa hơn nữa bằng cách loại bỏ các thông số bodyvà thông số không sử dụng request.)
Jonik

54

Không nhất thiết là cách nhỏ gọn nhất để làm điều này, nhưng IMO khá sạch sẽ

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

Chỉnh sửa, bạn có thể sử dụng @ResponseBody trong phương thức xử lý ngoại lệ nếu sử dụng Spring 3.1+, nếu không thì sử dụng một ModelAndViewhoặc một cái gì đó.

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


1
Xin lỗi, điều này dường như không hoạt động. Nó tạo ra HTTP 500 "lỗi máy chủ" với dấu vết ngăn xếp dài trong nhật ký: 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 representationCó thiếu điều gì trong câu trả lời không?
Jonik

Ngoài ra, tôi không hiểu đầy đủ về điểm xác định loại tùy chỉnh khác (MyError). Điều đó có cần thiết không? Tôi đang sử dụng Spring mới nhất (3.2.2).
Jonik

1
Nó làm việc cho tôi. Tôi sử dụng javax.validation.ValidationExceptionthay thế. (Mùa xuân 3.1.4)
Jerry Chen

Điều này khá hữu ích trong trường hợp bạn có lớp trung gian giữa dịch vụ của mình và máy khách nơi lớp trung gian có khả năng xử lý lỗi riêng. Cảm ơn bạn vì ví dụ này @Zutty
StormeHawke

Đây phải là câu trả lời được chấp nhận, vì nó di chuyển mã xử lý ngoại lệ ra khỏi luồng thông thường và nó ẩn
httpServlet

48

Tôi sẽ thay đổi việc thực hiện một chút:

Đầu tiên, tôi tạo một UnknownMatchException:

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

Lưu ý việc sử dụng @ResponseStatus , sẽ được Spring nhận ra ResponseStatusExceptionResolver. Nếu ngoại lệ được ném, nó sẽ tạo ra một phản hồi với trạng thái phản hồi tương ứng. (Tôi cũng có quyền tự do thay đổi mã trạng thái 404 - Not Foundmà tôi thấy phù hợp hơn cho trường hợp sử dụng này, nhưng bạn có thể sử dụng HttpStatus.BAD_REQUESTnếu bạn muốn.)


Tiếp theo, tôi sẽ thay đổi MatchServiceđể có chữ ký sau:

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

Cuối cùng, tôi sẽ cập nhật trình điều khiển và ủy quyền cho Spring MappingJackson2HttpMessageConverterđể tự động xử lý tuần tự hóa JSON (nó được thêm theo mặc định nếu bạn thêm Jackson vào đường dẫn lớp và thêm @EnableWebMvchoặc <mvc:annotation-driven />vào cấu hình của bạn, xem tài liệu tham khảo ):

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

Lưu ý, rất phổ biến để tách các đối tượng miền khỏi các đối tượng xem hoặc các đối tượng DTO. Điều này có thể dễ dàng đạt được bằng cách thêm một nhà máy DTO nhỏ trả về đối tượng JSON tuần tự hóa:

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

Tôi có 500 và tôi đăng nhập: ay 28, 2015 5:23:31 PM org.apache.cxf.interceptor.Ab tríchFaultChainInitiatorObserver onMessage SEVERE: Lỗi xảy ra trong quá trình xử lý lỗi, bỏ cuộc! org.apache.cxf.interceptor.Fault
dao cạo

Giải pháp hoàn hảo, tôi chỉ muốn thêm rằng tôi hy vọng rằng DTO là một thành phần Matchvà một số đối tượng khác.
Marco Sulla

32

Đây là một cách tiếp cận khác nhau. Tạo một Exceptionchú thích tùy chỉnh với @ResponseStatus, như cái sau.

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

    public NotFoundException() {
    }
}

Và ném nó khi cần thiết.

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

Kiểm tra tài liệu về mùa xuân tại đây: http://docs.spring.io/spring/docs/civerse/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions .


Cách tiếp cận này cho phép bạn chấm dứt thực thi mọi lúc mọi nơi trong stacktrace mà không phải trả về "giá trị đặc biệt" sẽ chỉ định mã trạng thái HTTP mà bạn muốn trả về.
Muhammad Gelbana

21

Như đã đề cập trong một số câu trả lời, có khả năng tạo một lớp ngoại lệ cho mỗi trạng thái HTTP mà bạn muốn trả về. Tôi không thích ý tưởng phải tạo một lớp cho mỗi trạng thái cho mỗi dự án. Đây là những gì tôi đã đưa ra thay thế.

  • Tạo một ngoại lệ chung chấp nhận trạng thái HTTP
  • Tạo một trình xử lý ngoại lệ Tư vấn điều khiển

Hãy lấy mã

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

Sau đó, tôi tạo một lớp tư vấn điều khiển

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

Để dùng nó

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

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


Phương thức rất tốt .. Thay vì một Chuỗi đơn giản, tôi thích trả về một jSON với các trường errorCode và thông báo ..
İsmail Yavuz

1
Đây phải là câu trả lời chính xác, một trình xử lý ngoại lệ chung và toàn cầu với mã trạng thái tùy chỉnh và thông báo: D
Pedro Silva

10

Tôi đang sử dụng điều này trong ứng dụng khởi động mùa xuân của tôi

@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

Cách dễ nhất là ném một 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
Câu trả lời tốt nhất: không cần thay đổi loại trả về và không cần tạo ngoại lệ của riêng bạn. Ngoài ra, FeedbackStatusException cho phép thêm thông báo lý do nếu cần.
Migs

Điều quan trọng cần lưu ý rằng FeedbackStatusException chỉ khả dụng trong phiên bản Mùa xuân 5+
Ethan Conner

2

Với Spring Boot, tôi không hoàn toàn chắc chắn tại sao điều này lại cần thiết (tôi đã nhận được /errordự phòng mặc dù @ResponseBodyđã được xác định trên một @ExceptionHandler), nhưng bản thân những điều sau đây không hoạt động:

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

Nó vẫn đưa ra một ngoại lệ, rõ ràng vì không có loại phương tiện sản xuất nào được định nghĩa là thuộc tính yêu cầu:

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

Vì vậy, tôi đã thêm chúng.

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

Và điều này đã cho tôi thông qua "loại phương tiện tương thích được hỗ trợ", nhưng sau đó nó vẫn không hoạt động, vì tôi ErrorMessageđã bị lỗi:

public class ErrorMessage {
    int code;

    String message;
}

JacksonMapper đã không xử lý nó là "có thể chuyển đổi", vì vậy tôi đã phải thêm getters / setters và tôi cũng đã thêm @JsonPropertychú thích

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

Sau đó tôi nhận được tin nhắn của mình như dự định

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

0

Bạn cũng có thể throw new HttpMessageNotReadableException("error description")được hưởng lợi từ việc xử lý lỗi mặc định của Spring .

Tuy nhiên, giống như trường hợp với các lỗi mặc định đó, sẽ không có phần phản hồi nào được đặt.

Tôi thấy những điều này hữu ích khi từ chối các yêu cầu chỉ có thể được làm thủ công một cách hợp lý, có khả năng cho thấy mục đích xấu, vì chúng che khuất thực tế rằng yêu cầu bị từ chối dựa trên xác nhận tùy chỉnh sâu hơn và tùy chỉnh của nó.

Hth, dtk


HttpMessageNotReadableException("error description")bị phản đối
Kuba imonovský

0

Một cách khác là sử dụng @ExceptionHandlervới @ControllerAdvicetập trung tất cả bộ xử lý của bạn trong cùng một lớp, nếu không bạn phải đặt các phương pháp xử lý trong mọi điều khiển bạn muốn quản lý một ngoại lệ.

Lớp xử lý của bạn:

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

Ngoại lệ tùy chỉnh của bạn:

public class MyBadRequestException extends RuntimeException {

  private String description;

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

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

Bây giờ bạn có thể đưa ra các ngoại lệ từ bất kỳ bộ điều khiển nào của bạn và bạn có thể định nghĩa các trình xử lý khác trong lớp tư vấn của bạn.


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.