Spring Boot 2 中的參數(shù)校驗(yàn) spring-boot-starter-validation/Hibernate Validator
Validation in Spring Boot
在springboot中常用的用于參數(shù)校驗(yàn)的注解如下:
@AssertFalse 所注解的元素必須是Boolean類型,且值為false
@AssertTrue 所注解的元素必須是Boolean類型,且值為true
@DecimalMax 所注解的元素必須是數(shù)字,且值小于等于給定的值
@DecimalMin 所注解的元素必須是數(shù)字,且值大于等于給定的值
@Digits 所注解的元素必須是數(shù)字,且值必須是指定的位數(shù)
@Future 所注解的元素必須是將來某個(gè)日期
@Max 所注解的元素必須是數(shù)字,且值小于等于給定的值
@Min 所注解的元素必須是數(shù)字,且值小于等于給定的值
@Range 所注解的元素需在指定范圍區(qū)間內(nèi)
@NotNull 所注解的元素值不能為null
@NotBlank 所注解的元素值有內(nèi)容
@Null 所注解的元素值為null
@Past 所注解的元素必須是某個(gè)過去的日期
@PastOrPresent 所注解的元素必須是過去某個(gè)或現(xiàn)在日期
@Pattern 所注解的元素必須滿足給定的正則表達(dá)式
@Size 所注解的元素必須是String、集合或數(shù)組,且長度大小需保證在給定范圍之內(nèi)
@Email 所注解的元素需滿足Email格式
一、添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
這個(gè)starter依賴的是Hibernate Validator。
二、實(shí)體類參數(shù)校驗(yàn)
(一)實(shí)體類上加上注解
import lombok.Data;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* @author chushiyan
* @email Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
* @description
*/
@Data
public class User implements Serializable {
private String id;
@NotNull(message = "姓名不能為空")
@Size(min = 1, max = 20, message = "姓名長度必須在1-20之間")
private String name;
@Min(value = 10, message = "年齡必須大于10")
@Max(value = 150, message = "年齡必須小于150")
private Integer age;
@Email(message = "郵箱格式不正確")
private String email;
}
(二)Controller中加上注解
在controller中使用@Valid 或者@Validated 注解校驗(yàn)
import com.chushiyan.validation_tutorial.entity.Result;
import com.chushiyan.validation_tutorial.pojo.User;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* @author chushiyan
* @email Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
* @description
*/
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public Result test(@Valid @RequestBody User user){
System.out.println(user);
return new Result(true,200,"");
}
}
(三)測試
使用postman發(fā)送POST請(qǐng)求:http://localhost:10000/user
{
"age":120,
"email":"chushiyan"
}
控制臺(tái)打?。?/p>
WARN 12476 --- [io-10000-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.chushiyan.validation_tutorial.entity.Result com.chushiyan.validation_tutorial.controller.UserController.test(com.chushiyan.validation_tutorial.pojo.User) with 2 errors: [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [姓名不能為空]] [Field error in object 'user' on field 'email': rejected value [chushiyan]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@20195c7a,.*]; default message [郵箱格式不正確]] ]
響應(yīng)的數(shù)據(jù):
{
"timestamp": "2019-12-03T09:14:00.759+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.user.name",
"NotNull.name",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"user.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "姓名不能為空",
"objectName": "user",
"field": "name",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
},
{
"codes": [
"Email.user.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"user.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"arguments": null,
"defaultMessage": ".*",
"codes": [
".*"
]
}
],
"defaultMessage": "郵箱格式不正確",
"objectName": "user",
"field": "email",
"rejectedValue": "chushiyan",
"bindingFailure": false,
"code": "Email"
}
],
"message": "Validation failed for object='user'. Error count: 2",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.chushiyan.validation_tutorial.entity.Result com.chushiyan.validation_tutorial.controller.UserController.test(com.chushiyan.validation_tutorial.pojo.User) with 2 errors: [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [姓名不能為空]] [Field error in object 'user' on field 'email': rejected value [chushiyan]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@20195c7a,.*]; default message [郵箱格式不正確]] \r\n\tat (博主進(jìn)行了省略......)",
"path": "/user"
}
(四)全局處理異常
上面響應(yīng)的錯(cuò)誤肯定是不夠友好的,所以需要進(jìn)行異常處理。這里定義一個(gè)全局處理函數(shù)
import com.chushiyan.validation_tutorial.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author chushiyan
* @email Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
* @description
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理所有校驗(yàn)失敗的異常(MethodArgumentNotValidException異常)
*
* @param ex
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
// 設(shè)置響應(yīng)狀態(tài)碼為400
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleBindGetException(MethodArgumentNotValidException ex) {
Map<String, Object> body = new LinkedHashMap<String, Object>();
body.put("timestamp", new Date());
// 獲取所有異常
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(x -> x.getDefaultMessage())
.collect(Collectors.toList());
body.put("errors", errors);
return new Result(false, 20001, "提交的數(shù)據(jù)校驗(yàn)失敗", body);
}
}
(五)再次測試
使用postman發(fā)送POST請(qǐng)求:http://localhost:10000/user
{
"age":120,
"email":"chushiyan"
}
響應(yīng)的json數(shù)據(jù):
{
"flag": false,
"code": 20001,
"message": "提交的數(shù)據(jù)校驗(yàn)失敗",
"data": {
"timestamp": "2019-12-03T09:35:02.815+0000",
"errors": [
"郵箱格式不正確",
"姓名不能為空"
]
}
}
三、單個(gè)參數(shù)校驗(yàn)
(一)直接在參數(shù)前加上校驗(yàn)注解:
package com.chushiyan.validation_tutorial.controller;
import com.chushiyan.validation_tutorial.entity.Result;
import com.chushiyan.validation_tutorial.pojo.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* @author chushiyan
* @email Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
* @description
*/
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@GetMapping
public Result test2(@NotNull(message = "name不能為空") String name){
System.out.println(name);
return new Result(true,200,"");
}
}
注意:需要在類上添加@Validated注解,否則不會(huì)校驗(yàn)。
(二)全局處理函數(shù)
import com.chushiyan.validation_tutorial.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author chushiyan
* @email Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
* @description
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理所有參數(shù)校驗(yàn)時(shí)拋出的異常
*
* @param ex
* @return
*/
@ExceptionHandler(value = ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleBindException(ValidationException ex) {
Map<String, Object> body = new LinkedHashMap<String, Object>();
body.put("timestamp", new Date());
// 獲取所有異常
List<String> errors = new LinkedList<String>();
if(ex instanceof ConstraintViolationException){
ConstraintViolationException exs = (ConstraintViolationException) ex;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
errors.add(item.getMessage());
}
}
body.put("errors", errors);
return new Result(true, 20001, "提交的參數(shù)校驗(yàn)失敗", body);
}
}
(二)測試
postman 測試http://localhost:10000/user GET
{
"flag": true,
"code": 20001,
"message": "提交的參數(shù)校驗(yàn)失敗",
"data": {
"timestamp": "2019-12-03T09:58:45.212+0000",
"errors": [
"name不能為空"
]
}
}
四、參數(shù)校驗(yàn)分組
在實(shí)際開發(fā)中經(jīng)常會(huì)遇到這種情況:添加用戶時(shí),id是由后端生成的,不需要校驗(yàn)id是否為空,但是修改用戶時(shí)就需要校驗(yàn)id是否為空。如果在接收參數(shù)的User實(shí)體類的id屬性上添加NotNull,顯然無法實(shí)現(xiàn)。這時(shí)候就可以定義分組,在需要校驗(yàn)id的時(shí)候校驗(yàn),不需要的時(shí)候不校驗(yàn)。
(一)定義表示組別的接口類
package com.chushiyan.validation_tutorial.validate;
public interface GroupA {
}
(二)在實(shí)體類的注解中標(biāo)記id使用上面定義的組
給id屬性添加分組:
package com.chushiyan.validation_tutorial.pojo;
import com.chushiyan.validation_tutorial.validate.GroupA;
import lombok.Data;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* @author chushiyan
* @email Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
* @description
*/
@Data
public class User implements Serializable {
@NotNull(groups = GroupA.class, message = "id不能為空")
private String id;
@NotNull(message = "姓名不能為空")
@Size(min = 1, max = 20, message = "姓名長度必須在1-20之間")
private String name;
@Min(value = 10, message = "年齡必須大于10")
@Max(value = 150, message = "年齡必須小于150")
private Integer age;
@Email(message = "郵箱格式不正確")
private String email;
}
(三)在controller中使用@Validated指定使用哪個(gè)組
@PostMapping
public Result add(@Validated @RequestBody User user) {
return new Result(true, 200, "增加用戶成功");
}
@PutMapping("/update")
// 指定GroupA,這樣就會(huì)校驗(yàn)id屬性是否為空
// 注意:還得必須添加Default.class,否則不會(huì)執(zhí)行其他的校驗(yàn)(如我們案例中的@Email)
public Result update(@Validated({GroupA.class, Default.class}) @RequestBody User user) {
return new Result(true, 200, "修改用戶成功");
}