我正在使用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
,因为我不需要它。到目前为止,一切都管理得很好。
答案 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"
}
]
}