Spring Boot中的数据校验
我们可能会经常需要对传入的参数进行校验,如果数据比较少的时候还比较容易处理,但当数据比较多的时候会显得比较麻烦,而且处理不当的时候,还会代码重复。这时候就需要Spring Boot对参数进行校验了。
这时候就需要使用使用Validation对数据进行校验了。
在Spring Boot中使用Validation
有如下的一段代码:
@RestController
public class BookController {
@PostMapping("/book")
public Book getBook(Book book) {
// ...
return book;
}
}
可以看到请求传入了一个Book,Book类如下:
public class Book {
private Integer id;
private String bookName;
private String bookAuthor;
// 省略get和set方法
}
现在需要对book中的属性进行校验,如果使用if else 进行校验会显得很麻烦。这时候可以在Book中的属性上添加Validation注解,如下:
public class Book {
private Integer id;
@NotBlank
private String bookName;
@NotBlank
private String bookAuthor;
// 省略get和set方法
}
其中的NotBlank注解指的是属性值不能为空。
然后还需要在Controller中的方法的需要校验的字段上加上@Valid注解,表明这个参数需要校验,如下:
@RestController
public class BookController {
@PostMapping("/book")
public Book getBook(@Valid Book book) {
// ...
return book;
}
}
这时候假如bookName和bookAuthor为空,那么就会出现400的响应码。
Spring Boot这时的返回为:
{
"timestamp": "2018-07-25T01:21:53.644+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.book.bookAuthor",
"NotBlank.bookAuthor",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"book.bookAuthor",
"bookAuthor"
],
"arguments": null,
"defaultMessage": "bookAuthor",
"code": "bookAuthor"
}
],
"defaultMessage": "不能为空",
"objectName": "book",
"field": "bookAuthor",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
},
{
"codes": [
"NotBlank.book.bookName",
"NotBlank.bookName",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"book.bookName",
"bookName"
],
"arguments": null,
"defaultMessage": "bookName",
"code": "bookName"
}
],
"defaultMessage": "不能为空",
"objectName": "book",
"field": "bookName",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='book'. Error count: 2",
"path": "/book"
}
因为使用的是测试工具,所以Spring Boot返回的是Json串。
上面的是Spring Boot的默认返回,我们肯定不希望返回这样的一个东西给前端。所以就需要自定义错误信息和错误处理。
自定义校验信息和错误处理
Validation注解中都有一个message的属性可以通过这个属性自定义错误信息。如下:
public class Book {
private Integer id;
@NotBlank(message = "书名不能为空")
private String bookName;
@NotBlank(message = "作者不能为空")
private String bookAuthor;
// 省略get和set方法
}
Spring Boot的Controller方法中可以传一个BindingResult或者Errors类型的参数,值得注意的一点是这个参数的位置必须是参数列表的被@Valid
注解修饰的参数紧跟着的(也就是被@Valid修饰的下一个参数
)后面,如果在前面,会出现下面的错误,如果不是下一个,那么这个参数将不起作用。
{
"timestamp": "2018-07-25T01:49:10.742+0000",
"status": 500,
"error": "Internal Server Error",
"message": "An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments to which they apply: public top.mcwebsite.demo.domain.Book top.mcwebsite.demo.web.controller.BookController.getBook(org.springframework.validation.BindingResult,top.mcwebsite.demo.domain.Book,javax.servlet.http.HttpServletResponse)",
"path": "/book"
}
下面的代码是如何进行自定义错误处理:
@RestController
public class BookController {
@PostMapping("/book")
public Book getBook(@Valid Book book, BindingResult result, HttpServletResponse response) {
if (result.hasErrors()) {
result.getAllErrors().forEach((error) -> {
FieldError fieldError = (FieldError) error;
// 属性
String field = fieldError.getField();
// 错误信息
String message = fieldError.getDefaultMessage();
System.out.println(field + ":" + message);
});
}
// ...
return book;
}
}
结果:
这里的错处处理只是简单的打印。
这里的错误消息也可以做成国际化的,具体做法可以自己取研究。
当需要校验的Bean比较多的时候这样也会显得比较麻烦,所以这时候就可以使用Spring Boot的异常处理机制了。
Spring Boot的异常处理+Bean Validation
Spring Boot 中有一个专门处理错误信息的一个类叫做ResponseEntityExceptionHandler。其中有很多关于400的错误处理,也就是参数错误的处理,其中就有一个专门用来处理没有通过校验的参数的方法。我们重写这个类的这个方法即可。
@ControllerAdvice // Spring 的异常处理的注解
public class BadRequestExceptionHandler extends ResponseEntityExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, String> messages = new HashMap<>();
BindingResult result = ex.getBindingResult();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
for (ObjectError error : errors) {
FieldError fieldError = (FieldError) error;
messages.put(fieldError.getField(), fieldError.getDefaultMessage());
}
logger.error(messages.toString());
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(messages);
}
}
这时候controller中的方法中就不需要BindingResult或者Errors类型的参数了。
一些常用的Validation注解
注解 | 作用 |
---|---|
@NotNull | 值不能为空 |
@Null | 值必须为空 |
@Pattern(regex=) | 字符串必须匹配正则表达式 |
@Size(min, max) | 集合元素的数量必须在min和max之间 |
@CreditCardNumber(ignoreNonDigitCharacters=) | 字符串必须是信用卡号,按找美国的标准验证 |
字符串必须是Email地址 | |
@Length(min, max) | 检查字符串的长度 |
@NotBlank | 字符串不能为空串 |
@NotEmpty | 字符串不能为null, 集合必须有元素 |
@Range(min, max) | 数字必须大于min, 小于max |
@SafeHtml | 字符串必须是安全的html |
@URL | 字符串必须是合法的URL |
@AssertFalse | 值必须是false |
@AssertTrue | 值必须是true |
@DecimalMax(value=, inclusive=) | 值必须小于等于(inclusive=true)/小于(inclusive=false)属性指定的值,也可以注释在字符串类型的属性上。 |
@DecimalMin(value=, inclusive=) | 值必须大于等于(inclusive=true)/小于(inclusive=false)属性指定的值,也可以注释在字符串类型的属性上。 |
@Digist(integer=,fraction=) | 数字格式检查。integer指定整数部分的最大长度,fraction指定小数部分的最大长度 |
@Future | 时间必须是未来的 |
@Past | 事件必须是过去的 |
@Max(value=) | 值必须小于等于value指定的值。不能注解在字符串类型属性上。 |
@Min(value=) | 值必须小于等于value指定的值。不能注解在字符串类型属性上。 |