如何使用@ControllerAdvise(和@RestControllerAdvise)类保留Spring的内置REST响应JSON主体?

pet*_*erl 12 java spring spring-mvc spring-boot

在Spring 4.x中,如果使用扩展的@RestControllerAdvise(或@ControllerAdvice),ResponseEntityExceptionHandler默认情况下,对于标记为的参数,不再返回具有良好且信息丰富的JSON响应主体的默认异常处理@Valid.

如何在使用ResponseEntityExceptionHandler基础时返回默认的JSON实体@RestControllerAdvice

以下是描述此问题的简单但完整的示例.使用这些类:

@RestController
class CarsController {

  @PostMapping("/cars")
  public void createCar(@RequestBody @Valid Car car) {
    System.out.println("Creating " + car);
    throw new WhateverException();
  }

  @ExceptionHandler(WhateverException.class)
  @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
  public void handleWhateverException(){
    System.out.println("Handling a WhateverException.");
  }

}

class Car {

  @NotNull
  private String make;

  @NotNull
  private String model;

  ...getter/setters omitted for brevity...
}

class WhateverException extends RuntimeException {}
Run Code Online (Sandbox Code Playgroud)

如果提交POST/cars

{
    "make": "BMW"
}
Run Code Online (Sandbox Code Playgroud)

它响应a 400和以下身体:

{
  "timestamp": 1491020374642,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
  "errors": [
    {
      "codes": [
        "NotNull.car.model",
        "NotNull.model",
        "NotNull.java.lang.String",
        "NotNull"
      ],
      "arguments": [
        {
          "codes": [
            "car.model",
            "model"
          ],
          "arguments": null,
          "defaultMessage": "model",
          "code": "model"
        }
      ],
      "defaultMessage": "may not be null",
      "objectName": "car",
      "field": "model",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotNull"
    }
  ],
  "message": "Validation failed for object='car'. Error count: 1",
  "path": "/cars"
}
Run Code Online (Sandbox Code Playgroud)

但是,如果将异常处理方法移动到它自己标记的类@RestControllerAdvice,该类扩展自ResponseEntityExceptionHandler如下所示:

@RestControllerAdvice
class RestExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(WhateverException.class)
  @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
  public void handleWhateverException(WhateverException e, HttpServletRequest httpServletRequest) {
    System.out.println("Handling a WhateverException.");
  }

}
Run Code Online (Sandbox Code Playgroud)

你会得到一个400空体,这是造成ResponseEntityExceptionHandler提供的方法(handleMethodArgumentNotValid(..)),它建立在身体是一个响应null.

你如何改变这个@RestControllerAdvice类来触发发生的原始处理,它提供了一个JSON主体来描述提交的请求无效的原因?

laf*_*ste 5

您可以在扩展中模拟spring 的行为:@ControllerAdviceResponseEntityExceptionHandler

@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body,
                             HttpHeaders headers, HttpStatus status, WebRequest request) {
    if (body == null) {
        body = ImmutableMap.builder()
                   .put("timestamp", LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond())
                   .put("status", status.value())
                   .put("error", status.getReasonPhrase())
                   .put("message", ex.getMessage())
                   .put("exception", ex.getClass().getSimpleName())  // can show FQCN like spring with getName()
                   .put("path", ((ServletWebRequest)request).getRequest().getRequestURI())
                   .build();
    }
    return super.handleExceptionInternal(ex, body, headers, status, request);
}
Run Code Online (Sandbox Code Playgroud)

*ImmutableMap需要番石榴。

**LocalDateTime需要 java8。


pvp*_*ran 4

这个怎么样?

   @RestControllerAdvice
    class RestExceptionHandler {

      @ExceptionHandler(org.springframework.validation.BindException.class)
      public ResponseEntity<String> handleBindException(org.springframework.validation.BindException e) {
         return ResponseEntity.badRequest().body(e.getMessage());
      }
   }
Run Code Online (Sandbox Code Playgroud)

@Valid失败时,它抛出 BindException。你可以这样处理。或者你可以这样做throw e,这会给你与之前抛出的完全相同的响应