Hello everyone, I am Misty.
A few days ago I wrote an article "How does SpringBoot unify the back-end return format?" Old birds all play like this! 》The reading effect is not bad, and it has been reprinted by many publishers. Today we continue to the second article to talk about how to integrate the parameter verification Validator in SprinBoot, and the high-level skills of parameter verification (custom verification, grouping check).
This article relies on the code foundation of the previous article, and a global exception checker has been added to the project. (The code warehouse is at the end of the article)
First of all, let's take a look at what is the Validator parameter validator, why do we need parameter validation?
Why need parameter verification
In the daily interface development, in order to prevent illegal parameters from affecting the business, it is often necessary to verify the interface parameters. For example, when logging in, you need to verify whether the username and password are empty. When creating a user, you need to verify the email and mobile phone. Whether the number format is accurate. Relying on the code to check the interface parameters one by one is too cumbersome, and the code readability is extremely poor.
The Validator framework is designed to solve the problem that developers write less code during development and improve development efficiency; Validator is specifically used for interface parameter verification, such as common required verification and email format verification. The user name must be between 6 and 12. Wait...
The Validator validation framework follows the JSR-303 validation specification (parameter validation specification), JSR is the abbreviation Java Specification Requests
Next, let's take a look at how to integrate the parameter verification framework in SpringbBoot.
Integrated parameter verification in SpringBoot
The first step is to introduce dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Note: Starting fromspringboot-2.3
, the verification package is independent of astarter
component, so validation and web need to be introduced, while thespringboot-2.3
only needs to introduce web dependency.
The second step is to define the entity class for parameter verification
@Data
public class ValidVO {
private String id;
@Length(min = 6,max = 12,message = "appId长度必须位于6到12之间")
private String appId;
@NotBlank(message = "名字为必填项")
private String name;
@Email(message = "请填写正确的邮箱地址")
private String email;
private String sex;
@NotEmpty(message = "级别不能为空")
private String level;
}
In actual development, you need to set the corresponding business prompts, that is, the message attribute, for the fields that need to be verified.
Common constraint annotations are as follows:
annotation | Function |
---|---|
@AssertFalse | Can be null, if not null, it must be false |
@AssertTrue | Can be null, if not null, it must be true |
@DecimalMax | The setting cannot exceed the maximum value |
@DecimalMin | The setting cannot exceed the minimum value |
@Digits | The setting must be a number and the number of digits of the integer and the number of decimals must be within the specified range |
@Future | The date must be in the future of the current date |
@Past | The date must be in the past of the current date |
@Max | Maximum must not exceed this maximum |
@Min | The maximum must not be less than this minimum |
@NotNull | Cannot be null, can be empty |
@Null | Must be null |
@Pattern | Must satisfy the specified regular expression |
@Size | The size() value of collection, array, map, etc. must be within the specified range |
Must be in email format | |
@Length | The length must be within the specified range |
@NotBlank | The string cannot be null, nor can it be equal to "" after the string trim() |
@NotEmpty | Cannot be null, size() of collection, array, map, etc. cannot be 0; after the string trim(), it can be equal to "" |
@Range | Value must be within the specified range |
@URL | Must be a URL |
Note: This table is only a simple description of the annotation function, and does not explain the attributes of each annotation; you can see the source code for details.
The third step is to define the verification class for testing
@RestController
@Slf4j
@Validated
public class ValidController {
@ApiOperation("RequestBody校验")
@PostMapping("/valid/test1")
public String test1(@Validated @RequestBody ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test1 valid success";
}
@ApiOperation("Form校验")
@PostMapping(value = "/valid/test2")
public String test2(@Validated ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test2 valid success";
}
@ApiOperation("单参数校验")
@PostMapping(value = "/valid/test3")
public String test3(@Email String email){
log.info("email is {}", email);
return "email valid success";
}
}
Here we first define three methods test1, test2, test3, test1 using @RequestBody
annotations to receive json data sent by the front end, test2 simulates form submission, and test3 simulates single parameter submission. Note that when using single parameter verification, you need to add @Validated annotations to the Controller, otherwise will not take effect.
The fourth step is to experience the effect
- Calling the test1 method, the prompt is
org.springframework.web.bind.MethodArgumentNotValidException
abnormal
POST http://localhost:8080/valid/test1
Content-Type: application/json
{
"id": 1,
"level": "12",
"email": "47693899",
"appId": "ab1c"
}
{
"status": 500,
"message": "Validation failed for argument [0] in public java.lang.String com.jianzh5.blog.valid.ValidController.test1(com.jianzh5.blog.valid.ValidVO) with 3 errors: [Field error in object 'validVO' on field 'email': rejected value [47693899]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@26139123,.*]; default message [不是一个合法的电子邮件地址]]...",
"data": null,
"timestamp": 1628239624332
}
- Calling the test2 method, the exception
org.springframework.validation.BindException
POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded
id=1&level=12&email=476938977&appId=ab1c
{
"status": 500,
"message": "org.springframework.validation.BeanPropertyBindingResult: 3 errors\nField error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; default message [名字为必填项]...",
"data": null,
"timestamp": 1628239301951
}
- Calling the test3 method, the exception
javax.validation.ConstraintViolationException
POST http://localhost:8080/valid/test3
Content-Type: application/x-www-form-urlencoded
email=476938977
{
"status": 500,
"message": "test3.email: 不是一个合法的电子邮件地址",
"data": null,
"timestamp": 1628239281022
}
By adding Validator
verification framework can help us to automatically achieve parameter verification.
Parameter exceptions are added to the global exception handler
Although we previously defined a global exception interceptor and saw that the interceptor is indeed effective, Validator
verification framework is too bloated and not easy to read. In order to facilitate the front-end prompt, we need to simplify it.
Directly modify the previously defined RestExceptionHandler
to intercept the three exceptions of parameter verification separately: javax.validation.ConstraintViolationException
, org.springframework.validation.BindException
, org.springframework.web.bind.MethodArgumentNotValidException
, the code is as follows:
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<ResultData<String>> handleValidatedException(Exception e) {
ResultData<String> resp = null;
if (e instanceof MethodArgumentNotValidException) {
// BeanValidation exception
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
ex.getBindingResult().getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof ConstraintViolationException) {
// BeanValidation GET simple param
ConstraintViolationException ex = (ConstraintViolationException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof BindException) {
// BeanValidation GET object param
BindException ex = (BindException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
ex.getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
}
return new ResponseEntity<>(resp,HttpStatus.BAD_REQUEST);
}
Experience effect
POST http://localhost:8080/valid/test1
Content-Type: application/json
{
"id": 1,
"level": "12",
"email": "47693899",
"appId": "ab1c"
}
{
"status": 400,
"message": "名字为必填项; 不是一个合法的电子邮件地址; appId长度必须位于6到12之间",
"data": null,
"timestamp": 1628435116680
}
Does it feel refreshed?
Custom parameter verification
Although the annotations provided by Spring Validation are basically sufficient, in the face of complex definitions, we still need to define the relevant solutions ourselves to achieve automatic verification.
For example, the sex attribute in the above entity class only allows the front end to pass the enumeration values M and F. How to implement it?
The first step is to create a custom annotation
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumString.List.class)
@Documented
@Constraint(validatedBy = EnumStringValidator.class)//标明由哪个类执行校验逻辑
public @interface EnumString {
String message() default "value not in enum values.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* @return date must in this value array
*/
String[] value();
/**
* Defines several {@link EnumString} annotations on the same element.
*
* @see EnumString
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
EnumString[] value();
}
}
The second step is to customize the verification logic
public class EnumStringValidator implements ConstraintValidator<EnumString, String> {
private List<String> enumStringList;
@Override
public void initialize(EnumString constraintAnnotation) {
enumStringList = Arrays.asList(constraintAnnotation.value());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value == null){
return true;
}
return enumStringList.contains(value);
}
}
The third step is to add comments to the field
@ApiModelProperty(value = "性别")
@EnumString(value = {"F","M"}, message="性别只允许为F或M")
private String sex;
The fourth step is to experience the effect
POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded
id=1&name=javadaily&level=12&email=476938977@qq.com&appId=ab1cdddd&sex=N
{
"status": 400,
"message": "性别只允许为F或M",
"data": null,
"timestamp": 1628435243723
}
Packet check
Certain fields of a VO object are required when it is added, but not required when it is updated. As above ValidVO
the id and appId properties when new operations are optional , and in the editing operation are required , when adding a new name for the operation required , the face of this How would you deal with the scene?
In the actual development, I saw many students create two VO objects, ValidCreateVO
, ValidEditVO
to handle this kind of scene. This can indeed achieve the effect, but it will cause class expansion, and it is extremely easy to be ridiculed by the development veterans.
In fact, the Validator
verification framework has considered this scenario and provided a solution, which is packet verification , but many students do not know it. To use packet verification, only three steps are required:
Step 1: Define the packet interface
public interface ValidGroup extends Default {
interface Crud extends ValidGroup{
interface Create extends Crud{
}
interface Update extends Crud{
}
interface Query extends Crud{
}
interface Delete extends Crud{
}
}
}
Here we define a grouping interface ValidGroup to inherit javax.validation.groups.Default
, and then define a number of different operation types in the grouping interface, Create, Update, Query, Delete. As for why we need to inherit Default, we will talk about it later.
The second step is to assign groups to the parameters in the model
@Data
@ApiModel(value = "参数校验类")
public class ValidVO {
@ApiModelProperty("ID")
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
private String id;
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
@ApiModelProperty(value = "应用ID",example = "cloud")
private String appId;
@ApiModelProperty(value = "名字")
@NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字为必填项")
private String name;
@ApiModelProperty(value = "邮箱")
@Email(message = "请填写正取的邮箱地址")
privte String email;
...
}
Specify a group for the parameter, and use the default group for those that do not specify a group.
The third step is to specify a group for the method that requires parameter verification
@RestController
@Api("参数校验")
@Slf4j
@Validated
public class ValidController {
@ApiOperation("新增")
@PostMapping(value = "/valid/add")
public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test3 valid success";
}
@ApiOperation("更新")
@PostMapping(value = "/valid/update")
public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test4 valid success";
}
}
Here we use the value
attribute to assign the Create and Update groups add()
and update()
The fourth step is to experience the effect
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
name=javadaily&level=12&email=476938977@qq.com&sex=F
We did not pass the id and appId parameters during Create, and the verification passed.
When we call the update method with the same parameters, it prompts a parameter verification error.
{
"status": 400,
"message": "ID不能为空; 应用ID不能为空",
"data": null,
"timestamp": 1628492514313
}
Since email belongs to the default group, and our group interface ValidGroup
has inherited the Default
, so it is also possible to check the parameters of the email field. like:
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
name=javadaily&level=12&email=476938977&sex=F
{
"status": 400,
"message": "请填写正取的邮箱地址",
"data": null,
"timestamp": 1628492637305
}
Of course, if your ValidGroup does not inherit the Default group, you need to add @Validated(value = {ValidGroup.Crud.Create.class, Default.class}
to the code attribute to make the verification of the email
summary
Parameter verification is used very frequently in actual development, but many students still just stay in simple use, such as group verification, custom parameter verification, these two high-level skills are basically not used, such as establishment Multiple VOs are used to accept the Create and Update scenarios. It is easy to be despised and ridiculed by veterans. I hope you can master it.
The source code of the old bird series has been uploaded to GitHub, and the required ones are obtained in the public number [JAVA Japanese Record] Reply to the keyword 0923
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。