Spring REST WebService校验失效,抛出HttpMessageNotWritableException异常
HttpMessageNotWritableException异常 嘿,我刚看完你的问题,这个异常其实很好定位和解决!
问题回顾
你在给Spring REST API加参数校验的时候,碰到了这个头疼的异常:
Apr 10, 2020 12:05:26 PM org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver doResolveHandlerMethodException
WARNING: Failed to invoke @ExceptionHandler method: public com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation com.luv2code.springdemo.exceptionhandling.GlobalExceptionHandler.processValidationError(org.springframework.web.bind.MethodArgumentNotValidException)
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation
先把你贴的相关代码整理出来:
实体类Customer
@Entity @Table(name="customer") public class Customer { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="id") private int id; @Column(name="first_name") @NotNull(message = "Firstname is necessary") @Size(min=1,message="This field is required") private String firstName; @Column(name="last_name") @NotNull(message = "Lastname is necessary") @Size(min=1,message="This field is required") private String lastName; @Column(name="email") private String email; // getters and setters }
错误响应类RestFieldError
public class RestFieldError { private String field; private String message; public RestFieldError() { } // getters and setters }
错误响应包装类RestFieldErrorValidation
public class RestFieldErrorValidation { private List<RestFieldError> fieldErrors = new ArrayList<>(); public RestFieldErrorValidation() { } public void addFieldError(String path, String message) { RestFieldError error = new RestFieldError(path, message); fieldErrors.add(error); } }
RestController代码
@RestController @RequestMapping("/api") public class CustomerRestController { @Autowired private CustomerService customerService; @InitBinder public void initBinder(WebDataBinder dataBinder) { System.out.println("Entered init binder"); StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true); dataBinder.registerCustomEditor(String.class, stringTrimmerEditor); } @PostMapping("/customers") @ResponseBody public Customer addCustomer(@Valid @RequestBody Customer theCustomer) { System.out.println("theCustomer :"+theCustomer.getFirstName()); theCustomer.setId(0); customerService.saveCustomer(theCustomer); return theCustomer; } }
全局异常处理器GlobalExceptionHandler
@ControllerAdvice public class GlobalExceptionHandler { private MessageSource messageSource; @Autowired public GlobalExceptionHandler(MessageSource messageSource) { this.messageSource = messageSource; } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public RestFieldErrorValidation processValidationError(MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List<FieldError> fieldErrors = result.getFieldErrors(); return processFieldErrors(fieldErrors); } private RestFieldErrorValidation processFieldErrors(List<FieldError> fieldErrors) { RestFieldErrorValidation dto = new RestFieldErrorValidation(); for (FieldError fieldError: fieldErrors) { String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError); dto.addFieldError(fieldError.getField(), localizedErrorMessage); } return dto; } private String resolveLocalizedErrorMessage(FieldError fieldError) { Locale currentLocale = LocaleContextHolder.getLocale(); String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale); if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) { String[] fieldErrorCodes = fieldError.getCodes(); localizedErrorMessage = fieldErrorCodes[0]; } return localizedErrorMessage; } }
问题根源
别急,这个异常的原因其实很简单:Spring没办法把你的RestFieldErrorValidation对象转成JSON响应!
具体原因有两个:
RestFieldErrorValidation类里的fieldErrors属性没有对应的getter方法,Spring的消息转换器(比如Jackson)无法读取这个属性的值;RestFieldError类不仅缺少带参数的构造方法(你在addFieldError里直接调用了new RestFieldError(path, message),但原类里没有这个构造),也可能没有完整的getter方法,导致即使创建了对象,也无法被序列化。
修复方案
只需要修改两个错误响应类就行,非常简单:
- 完善
RestFieldError类,补上带参数的构造和属性getter:
public class RestFieldError { private String field; private String message; public RestFieldError() { } // 补上这个带参数的构造,匹配你在addFieldError里的调用 public RestFieldError(String field, String message) { this.field = field; this.message = message; } // 添加getter方法,让Jackson能读取属性值 public String getField() { return field; } public String getMessage() { return message; } }
- 给
RestFieldErrorValidation类添加fieldErrors的getter:
public class RestFieldErrorValidation { private List<RestFieldError> fieldErrors = new ArrayList<>(); public RestFieldErrorValidation() { } public void addFieldError(String path, String message) { RestFieldError error = new RestFieldError(path, message); fieldErrors.add(error); } // 新增这个getter,暴露fieldErrors属性给序列化器 public List<RestFieldError> getFieldErrors() { return fieldErrors; } }
额外检查点
- 如果你是Spring Boot项目,
spring-boot-starter-web已经自带了Jackson依赖,不用额外加;如果是普通Spring项目,记得引入Jackson的核心依赖(比如com.fasterxml.jackson.core:jackson-databind); - 可以测试一下参数校验的场景,看看返回的错误响应是不是符合你的预期,顺便验证
GlobalExceptionHandler里的消息解析逻辑有没有问题。
内容的提问来源于stack exchange,提问作者Mohit Nirwan

