Spring Boot Web:使用 @Valid 校验请求数据


#Spring Boot


我们在 Spring Boot Web:处理 HTTP 请求体中的 JSON 数据 的基础上看下如何使用 @Valid 验证请求数据。

示例1:基础示例

项目结构:

.
├── build.gradle
└── src
    └── main
        ├── java
        │   └── hello
        │       ├── Application.java
        │       ├── BaseResponse.java
        │       ├── GreetingController.java
        │       ├── GreetingRequest.java
        │       └── GreetingResponse.java
        └── resources

修改 GreetingRequest :

package hello;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
public class GreetingRequest {

    @NotNull(message = "msg不能为空")
    @Size(min = 1, message = "msg长度不能小于1")
    private String msg;

}

修改 GreetingController :

package hello;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping(value = "/greeting1")
    public BaseResponse greeting1(@Valid @RequestBody GreetingRequest request) {
        return BaseResponse.success("", new GreetingResponse(counter.incrementAndGet(), String.format(template, request.getMsg())));
    }

}

运行 Application 类。使用Postman构建 POST 请求到http://127.0.0.1:8080/greeting1,请求体是json:

{"msg": ""}

响应体:

{
    "timestamp": "2018-07-23T00:57:19.507+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Size.greetingRequest.msg",
                "Size.msg",
                "Size.java.lang.String",
                "Size"
            ],
            "arguments": [
                {
                    "codes": [
                        "greetingRequest.msg",
                        "msg"
                    ],
                    "arguments": null,
                    "defaultMessage": "msg",
                    "code": "msg"
                },
                2147483647,
                1
            ],
            "defaultMessage": "msg长度不能小于1",
            "objectName": "greetingRequest",
            "field": "msg",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "Size"
        }
    ],
    "message": "Validation failed for object='greetingRequest'. Error count: 1",
    "path": "/greeting1"
}

是的,这个响应并没有用我们自定义的 BaseResponse 来包装。如何包装呢?看下面。

示例2:使用 ExceptionHandler 捕捉 valid 异常

我们希望把错误信息msg长度不能小于1放到BaseResponse中返回。怎么办? 加一个 ExceptionHandler 。

新建类 CustomExceptionHandler :

package hello;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class CustomExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        String msg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); // todo: 需要加null判断
        return BaseResponse.fail("请求数据有误: "+msg, null);
    }

}

运行 Application 类。使用Postman构建 POST 请求到http://127.0.0.1:8080/greeting1,请求体是json:

{"msg": ""}

响应体:

{
    "success": false,
    "msg": "msg长度不能小于1",
    "result": null
}

示例3:使用 BindingResult 捕捉 valid 错误

对于 GreetingController 中的greeting1函数,如果参数增加一个 BindingResult :

    @RequestMapping(value = "/greeting1")
    public BaseResponse greeting1(@Valid @RequestBody GreetingRequest request, BindingResult bindingResult) {
        return BaseResponse.success("", new GreetingResponse(counter.incrementAndGet(), String.format(template, request.getMsg())));
    }

那么,错误信息会被放进 bindingResult 。而示例2中的@ExceptionHandler就无效了。

这种方法适合返回数据是渲染的HTML网页的场景。可以看下 https://spring.io/guides/gs/validating-form-input/ 。


( 本文完 )