Add new field in body exception spring rest

2019-03-28 02:20发布

问题:

I want to handle exceptions in my Rest spring boot application. I know that with @ControllerAdvice and ResponseEntity I can return a custom object that will represent my error, but what I want is to add a new field to the body of the exesting exception that's all.

I created a custom Exception that inherit RuntimeException with an extra attribute, a list of string :

@ResponseStatus(HttpStatus.CONFLICT)
public class CustomException extends RuntimeException {

    private List<String> errors = new ArrayList<>();

    public CustomException(List<String> errors) {
        this.errors = errors;
    }

    public CustomException(String message) {
        super(message);
    }

    public CustomException(String message, List<String> errors) {
        super(message);
        this.errors = errors;
    }

    public List<String> getErrors() {
        return errors;
    }

    public void setErrors(List<String> errors) {
        this.errors = errors;
    }
}

In my controller I just throw this custom exception this way:

@GetMapping("/appointment")
public List<Appointment> getAppointments() {
    List<String> errors = new ArrayList<>();
    errors.add("Custom message");
    throw new CustomException("This is my message", errors);
}

When I test my Rest endpoint with postman, it seems like that spring boot doesn't marshall my errors field, the response is :

{
  "timestamp": "2017-06-05T18:19:03",
  "status": 409,
  "error": "Conflict",
  "exception": "com.htech.bimaristan.utils.CustomException",
  "message": "This is my message",
  "path": "/api/agenda/appointment"
}

I can go for a custom object with @ControllerAdvice if I can get the "path" and "timestamp" fields from the exception but there's no getters for these two attributes.

Thank you.

回答1:

Well! Here is the implementation of "path" and "timestamp" in DefaultErrorAttributes which you could do it in your custom implementation too:

Path:

String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");
if (path != null) {
    errorAttributes.put("path", path);
}

Timestamp:

errorAttributes.put("timestamp", new Date());

The documentation on error customization in spring boot is here.

@Bean
public ErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes() {
        @Override
        public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
            Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
            // customize here
            return errorAttributes;
        }

   };
}

Or you could write a custom implementation:

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
        // customize here
        return errorAttributes;
    }
}

The ErrorAttributes bean customizes the error response below:

{
   "timestamp": 1413883870237,
   "status": 500,
   "error": "Internal Server Error",
   "exception": "org.example.ServiceException",
   "message": "somthing goes wrong",
   "path": "/index"
}

The "exception" attribute can be customized using the @ExceptionHandler. A @ControlerAdvice could be used to customize the exception generically across controllers. To customize at the Controller level, you could place them within the controller.

In your case:

   @ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Invalid Inputs")
    @ExceptionHandler(CustomException.class)
    private void errorHanlder() {
        //Log exception
    }


  public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
    Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
    Throwable error = getError(requestAttributes);
    if (error instanceof CustomException) {
        errorAttributes.put("errorList", ((CustomException)error).getErrors());
    }
    return errorAttributes;
}


回答2:

Previous answer really has it all there but somehow it took me a while to figure it out, so in summary basically the simplest way to accomplish this is to have a bean like this:

@Bean
public ErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes() {
        @Override
        public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
                boolean includeStackTrace) {
            Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
            Throwable error = getError(requestAttributes);
            if (error instanceof CustomExceptionthere) {
                errorAttributes.put("errorList", ((CustomException)error).getErrors());
            }
            return errorAttributes;
        }

    };