REST控制器中的Spring Boot绑定和验证错误处理

Jaa*_*tum 10 validation spring spring-mvc spring-boot spring-restcontroller

当我有以下模型与JSR-303(验证框架)注释:

public enum Gender {
    MALE, FEMALE
}

public class Profile {
    private Gender gender;

    @NotNull
    private String name;

    ...
}
Run Code Online (Sandbox Code Playgroud)

以及以下JSON数据:

{ "gender":"INVALID_INPUT" }
Run Code Online (Sandbox Code Playgroud)

在我的REST控制器中,我想处理绑定错误(gender属性的无效枚举值)和验证错误(name属性不能为null).

以下控制器方法不起作用:

@RequestMapping(method = RequestMethod.POST)
public Profile insert(@Validated @RequestBody Profile profile, BindingResult result) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

这会com.fasterxml.jackson.databind.exc.InvalidFormatException在绑定或验证发生之前产生序列化错误.

经过一番摆弄后,我想出了这个自定义代码,它可以满足我的需求:

@RequestMapping(method = RequestMethod.POST)
public Profile insert(@RequestBody Map values) throws BindException {

    Profile profile = new Profile();

    DataBinder binder = new DataBinder(profile);
    binder.bind(new MutablePropertyValues(values));

    // validator is instance of LocalValidatorFactoryBean class
    binder.setValidator(validator);
    binder.validate();

    // throws BindException if there are binding/validation
    // errors, exception is handled using @ControllerAdvice.
    binder.close(); 

    // No binding/validation errors, profile is populated 
    // with request values.

    ...
}
Run Code Online (Sandbox Code Playgroud)

基本上这个代码的作用是序列化为通用映射而不是模型,然后使用自定义代码绑定到模型并检查错误.

我有以下问题:

  1. 自定义代码是这样的,或者在Spring Boot中有更标准的方法吗?
  2. @Validated注释如何工作?如何创建自己的自定义注释,@Validated以便封装我的自定义绑定代码?

Ali*_*ani 8

通常当 Spring MVC 无法读取 http 消息(例如请求正文)时,它会抛出一个HttpMessageNotReadableException异常实例。因此,如果 spring 无法绑定到您的模型,它应该抛出该异常。此外,如果您没有BindingResult在方法参数中定义每个待验证模型之后,则在验证错误的情况下,spring 将抛出MethodArgumentNotValidException异常。有了这一切,您可以创建ControllerAdvice捕获这两个异常并以您想要的方式处理它们。

@ControllerAdvice(annotations = {RestController.class})
public class UncaughtExceptionsControllerAdvice {
    @ExceptionHandler({MethodArgumentNotValidException.class, HttpMessageNotReadableException.class})
    public ResponseEntity handleBindingErrors(Exception ex) {
        // do whatever you want with the exceptions
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这里的缺点是当发生绑定错误时你不会得到 BindingResult。即,您可以对 MethodArgumentNotValidException 异常执行 ex.getBindingResult(),但不能对 HttpMessageNotReadableException 异常执行。 (2认同)

al_*_*har 6

这是我在一个项目中用于在春季启动时验证REST api的代码,这与您的要求不同,但是相同。

@RequestMapping(value = "/person/{id}",method = RequestMethod.PUT)
@ResponseBody
public Object updatePerson(@PathVariable Long id,@Valid Person p,BindingResult bindingResult){
    if (bindingResult.hasErrors()) {
        List<FieldError> errors = bindingResult.getFieldErrors();
        List<String> message = new ArrayList<>();
        error.setCode(-2);
        for (FieldError e : errors){
            message.add("@" + e.getField().toUpperCase() + ":" + e.getDefaultMessage());
        }
        error.setMessage("Update Failed");
        error.setCause(message.toString());
        return error;
    }
    else
    {
        Person person = personRepository.findOne(id);
        person = p;
        personRepository.save(person);
        success.setMessage("Updated Successfully");
        success.setCode(2);
        return success;
    }
Run Code Online (Sandbox Code Playgroud)

成功.java

public class Success {
int code;
String message;

public int getCode() {
    return code;
}

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

public String getMessage() {
    return message;
}

public void setMessage(String message) {
    this.message = message;
}
}
Run Code Online (Sandbox Code Playgroud)

错误.java

public class Error {
int code;
String message;
String cause;

public int getCode() {
    return code;
}

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

public String getMessage() {
    return message;
}

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

public String getCause() {
    return cause;
}

public void setCause(String cause) {
    this.cause = cause;
}

}
Run Code Online (Sandbox Code Playgroud)

您也可以在这里看看:Spring REST Validation


Jan*_*ing 6

你不能用@RequestBody 得到 BindException。不在带有Errors方法参数的控制器中,如此处所述:

Errors, BindingResult用于访问来自命令对象(即@ModelAttribute 参数)的验证和数据绑定的错误或来自@RequestBody 或@RequestPart 参数的验证的错误。您必须在经过验证的方法参数之后立即声明 Errors 或 BindingResult 参数。

它指出,@ModelAttribute您会收到绑定和验证错误,而您只会@RequestBody收到验证错误

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods

并在这里讨论:

https://github.com/spring-projects/spring-framework/issues/11406?jql=text%2520~%2520%2522RequestBody%2520binding%2522

对我来说,从用户的角度来看,这仍然没有意义。获取 BindExceptions 以向用户显示正确的错误消息通常非常重要。论点是,无论如何您都应该进行客户端验证。但如果开发人员直接使用 API,则情况并非如此。

假设您的客户端验证基于 API 请求。您想根据保存的日历检查给定日期是否有效。您将日期和时间发送到后端,但它失败了。

您可以使用 ExceptionHANdler 对 HttpMessageNotReadableException 做出反应来修改您获得的异常,但由于此异常,我无法像 BindException 那样正确访问哪个字段引发错误。我需要解析异常消息才能访问它。

所以我没有看到任何解决方案,这有点糟糕,因为@ModelAttribute它很容易出现绑定和验证错误。