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



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

使用基于ResponseEntityExceptionHandler@RestControllerAdvice时,如何获取要返回的默认 JSON 正文?

下面是一个简单但完整的示例来描述这个问题。使用这些类:

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

如果您提交POST/cars

{
"make": "BMW"
}

它以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"
}

但是,如果将异常处理方法移动到它自己的标记为@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.");
}
}

你会得到一个空400,这是由ResponseEntityExceptionHandler提供一个方法(handleMethodArgumentNotValid(..))引起的,该方法在身体null的地方构建一个响应。

您将如何更改此@RestControllerAdvice类以触发发生的原始处理,该处理提供了一个描述提交请求无效原因的 JSON 正文?

你可以模仿春天的行为在你的@ControllerAdvice扩展ResponseEntityExceptionHandler

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

*ImmutableMap需要番石榴。

**LocalDateTime需要java8。

这个怎么样?

@RestControllerAdvice
class RestExceptionHandler {
@ExceptionHandler(org.springframework.validation.BindException.class)
public ResponseEntity<String> handleBindException(org.springframework.validation.BindException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}

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

一种对我有用的方法是不扩展ResponseEntityExceptionHandler

因此,结果类将如下所示:

@RestControllerAdvice
class RestExceptionHandler {
@ExceptionHandler(WhateverException.class)
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public ResponseEntity<String> handleWhateverException(WhateverException e, HttpServletRequest httpServletRequest) {
System.out.println("Handling a WhateverException.");
return new ResponseEntity(...);
}    
}

但另请参阅:映射为 [class org.springframework.web.bind.MethodArgumentNotValidException] 的模棱两可@ExceptionHandler方法]

我遇到了同样的问题,在花费大量时间调试问题后,我发现杰克逊没有正确反序列化ErrorResponse对象。

这是因为我没有为ErrorResponse对象中定义的字段添加 getter 和 setter。我正在使用构造函数初始化字段,并且没有为这些字段定义 getter 和 setter。

解决方案(空体):

所以当我更新我的ErrorResponse对象时

import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;
@JsonRootName("error")
public class ErrorResponse {
private String message;
private List<String> details;
public ErrorResponse(String message, List<String> details) {
this.message = message;
this.details = details;
}
}

到下面的一个与吸气剂和二传手

import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;
@JsonRootName("error")
public class ErrorResponse {
private String message;
private List<String> details;
public ErrorResponse(String message, List<String> details) {
this.message = message;
this.details = details;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<String> getDetails() {
return details;
}
public void setDetails(List<String> details) {
this.details = details;
}
}

杰克逊现在正在正确反序列化ErrorResponse,我在响应中获得了序列化的正文。

最新更新