Spring Data REST阻止InvalidFormatException

时间:2017-08-30 16:14:36

标签: java spring spring-mvc spring-data-rest

我正在使用Spring Boot 1.5.6和Spring Data REST。我正在使用从SDR自动创建的PATCH端点作为我的一个模型bean。 我的bean有一些整数字段,我试图在purpuse中设置一个字符串值。 我收到的是这样的例外:

    {
  "cause": {
    "cause": null,
    "message": "Can not deserialize value of type int from String \"500s\": not a valid Integer value\n at [Source: N/A; line: -1, column: -1] (through reference chain: it.server.model.checkpoints.CheckPoint[\"passStockAlert\"])"
  },
  "message": "Could not read payload!; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type int from String \"500s\": not a valid Integer value\n at [Source: N/A; line: -1, column: -1] (through reference chain: it.server.model.checkpoints.CheckPoint[\"passStockAlert\"])"
}

我的客户是Angular,我希望客户收到更优雅的信息。我的异常通过messages.properties文件本地化,但在这种情况下,我不能只显示一般消息。 我应该指出哪个领域是错的以及为什么。

这听起来像是我的验证例外。这是我的豆子:

    @Entity
public class CheckPoint extends AbstractEntity {
    private static final long serialVersionUID = 2719798641638659883L;

    @NotNull(message = "The checkpoint must have a name")
    @Column(nullable = false, unique = true)
    private String name;

    private LocalTime openingTime;

    private LocalTime closingTime;

    @Min(value = 0, message = "The min pass stock alert must be 0")
    @Column(nullable = false)
    private int passStockAlert = 0;

有没有办法将此异常视为当您尝试在字段passStockAlert中设置小于0的值时抛出的异常? 确切地说,在这种情况下提出的例外是这样的:

    {
  "errors": [
    {
      "entity": "CheckPoint",
      "property": "passStockAlert",
      "invalidValue": -1,
      "message": "The min pass stock alert must be 0"
    }
  ]
}

====更多澄清=====

目前我正在使用例外布局的自定义:

    /**
 * According to https://github.com/spring-projects/spring-boot/issues/6555, this
 * is the standard way to customize Spring MVC exceptions.
 * 
 * In this case we customized the exception adding localization to the message
 * and adding details about the cause of the error that can be useful for
 * developers.
 * 
 * @author Daniele Renda
 *
 */
public class CustomErrorAttributes extends DefaultErrorAttributes {
    private Logger log = LogManager.getLogger();

    @Autowired
    private MessageSource messageSource;

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Locale locale = LocaleContextHolder.getLocale();
        Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
        Throwable throwable = getError(requestAttributes);

        /**
         * Adding the cause if present
         */
        if (throwable != null && throwable.getCause() != null) {
            Throwable cause = throwable.getCause();
            Map<String, Object> causeErrorAttributes = new HashMap<>();
            causeErrorAttributes.put("exception", cause.getClass().getName());
            causeErrorAttributes.put("message", cause.getMessage());
            errorAttributes.put("cause", causeErrorAttributes);
        }

        if (throwable != null) {
            boolean customizeMessage = false;

            if (throwable instanceof InvalidDataAccessApiUsageException) {
                customizeMessage = true;
            }

            /**
             * Override the messages of these exceptions
             */
            if (customizeMessage) {
                String localizedMessage = localizedMessage(throwable, locale);
                if (localizedMessage != null)
                    errorAttributes.put("message", localizedMessage);
            }
        }
        return errorAttributes;
    }

    private String localizedMessage(Throwable throwable, Locale locale) {
        if (throwable != null)
            return messageSource.getMessage(throwable.getClass().getName(), new Object[] {}, locale);
        return null;
    }

}

我正在使用验证监听器:

@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
    validatingListener.addValidator("beforeCreate", validator);
    validatingListener.addValidator("beforeSave", validator);
}

目前我没有使用任何@RestControllerAdvice,因为我不需要它。到目前为止,一切都管理得很好。

1 个答案:

答案 0 :(得分:1)

不确定我离我有多远,但我认为无论如何我都会提供详细信息。鉴于您已经拥有了从DefaultErrorAttributes.getError获取throwable的逻辑,您可以确定它的类型是否为InvalidFormatException并正确处理它。在我的样本中,我做了以下

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {

    @Autowired
    private MessageSource messageSource;

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Locale locale = LocaleContextHolder.getLocale();
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put("timestamp", new Date());
        addStatus(errorAttributes, requestAttributes);

        Throwable throwable = getError(requestAttributes);      
        if (throwable instanceof BindingResult) {
            addErrors(errorAttributes, (BindingResult) throwable, locale);
        } else if (throwable instanceof MethodArgumentNotValidException) {
            addErrors(errorAttributes, ((MethodArgumentNotValidException) throwable).getBindingResult(), locale);
        } else if (throwable instanceof InvalidFormatException) {
            addErrors(errorAttributes, (InvalidFormatException) throwable, locale);

        }
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes,
            RequestAttributes requestAttributes) {
        Integer status = getAttribute(requestAttributes,
                "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
            return;
        }
        errorAttributes.put("status", status);
        try {
            errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
        }
        catch (Exception ex) {
            // Unable to obtain a reason
            errorAttributes.put("error", "Http Status " + status);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    }

    private void addErrors(
            Map<String, Object> errorAttributes, BindingResult bindingResult, Locale locale) {
        List<ErrorDTO> errors = new ArrayList<>();
        for (ObjectError error : bindingResult.getAllErrors()) {
            ErrorDTO e = new ErrorDTO();
            e.setCode(error.getCode());
            e.setMessage(localizedMessage(error, locale));

            if (error instanceof FieldError) {
                FieldError fieldError = (FieldError) error;
                e.setField(fieldError.getField());
                e.setRejectedValue(fieldError.getRejectedValue());
            }
            errors.add(e);
        }
        errorAttributes.put("errors", errors);
    }

    private String getInvalidFormatExceptionFieldName(InvalidFormatException ex) {

        for (JsonMappingException.Reference r : ex.getPath()) {
            return r.getFieldName();
        }

        return null;
    }

    private void addErrors(
            Map<String, Object> errorAttributes, InvalidFormatException ex, Locale locale) {
        List<ErrorDTO> errors = new ArrayList<>();

        ErrorDTO e = new ErrorDTO();
        e.setCode("InvalidFormatException");
        String message = localizedMessage(
                "InvalidFormatException", 
                new Object[] {ex.getTargetType().getName(), ex.getValue()},
                locale);
        e.setMessage(message);
        e.setField(getInvalidFormatExceptionFieldName(ex));
        e.setRejectedValue(ex.getValue());
        errors.add(e);
        errorAttributes.put("errors", errors);

    }

    private String localizedMessage(ObjectError error, Locale locale) {
        return messageSource.getMessage(error, locale);
    }

    private String localizedMessage(String message, Object[] args, Locale locale) {
        return messageSource.getMessage(message, args, locale);
    }
}

专注于InvalidFormatException异常。我将throwable转换为InvalidFormatException,然后允许我同时获取字段名称,类型和值。我不是DefaultErrorAttributes提供的默认地图的粉丝,所以我创建了一个自定义的ErrorDTO,看起来像

public class ErrorDTO {

    private String code;
    private String message;
    private String field;
    private Object rejectedValue;

    public ErrorDTO() {

    }

    public ErrorDTO(String code, String message) {
        this(code, message, null, null);
    }

    public ErrorDTO(String code, String message, String field, Object rejectedValue) {
        this.code = code;
        this.message = message;
        this.field = field;
        this.rejectedValue = rejectedValue;
    }

    public String getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

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

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public Object getRejectedValue() {
        return rejectedValue;
    }

    public void setRejectedValue(Object rejectedValue) {
        this.rejectedValue = rejectedValue;
    }

}

要获取本地化消息,我会在自定义消息密钥中传递,&#34; InvalidFormatException&#34;它位于ValidationMessages.properties文件中并定义为

InvalidFormatException={1} is not valid for type {0}

以及作为对象数组的目标类型和值以及messageSource.getMessage

的语言环境

这将产生

的JSON响应
{
   "timestamp":1504310911502,
   "status":999,
   "error":"None",
   "errors":[
      {
         "code":"InvalidFormatException",
         "message":"500s is not valid for type int",
         "field":"passStockAlert",
         "rejectedValue":"500s"
      }
   ]
}