1. What is Bean Validation?
Bean Validation is a runtime data validation framework, and validation error messages are returned immediately after validation. JAVA released JSR303 and the contents of the validation package under javax in JAVAEE 6 in 2009. The main goal of this work is to provide Java application developers with Java object-based Constraints declarations and validators, as well as constraint metadata repositories and query APIs. However, there is no specific implementation of this, the Hibernate-Validator framework provides an implementation of all the built-in constraints in the JSR 303 specification, plus a few additional constraints.
Commonly used Validation annotations
Some of the most common validation annotations are as follows:
@NotNull
: The tag field can't benull
@NotEmpty
: Tag collection field is not empty (at least one element)@NotBlank
: The tag field string field cannot be an empty string (i.e. it must have at least one character)@Min
/@Max
: The Tag Number type field must be greater than/less than the specified value@Pattern
: The tag string field must match the specified regular expression@Email
: The tag string field must be a valid email address
2. Code engineering
The goal of the experiment is to verify the parameters passed by the controller
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>validtion</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
</project>
controller
Spring automatically maps the incoming JSON to Java object parameters. Now, we’re going to verify that the incoming Java object satisfies the constraints we pre-defined. To validate the request entity for incoming HTTP requests, we use it in the REST controller @Valid
The annotation marks the request entity:
package com.et.validation.controller;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.et.validation.entity.UserInfoReq;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public Map<String, Object> showHelloWorld(@RequestBody @Validated UserInfoReq req){
Map<String, Object> map = new HashMap<>();
map.put("msg", JSONUtil.toJsonStr(req));
return map;
}
}
entity
We have an int type field, and its value must be between 1 and 10200, such as@Min
and@Max
annotations. We also have a String type field, which must be an IP address, as such@Pattern
ANNOTATIONS IN THE REGEX (REGEX ACTUALLY STILL ALLOW INVALID IP ADDRESSES GREATER THAN 255, BUT WE'LL FIX THIS LATER IN THIS TUTORIAL WHEN WE CREATE A CUSTOM VALIDATOR).
package com.et.validation.entity;
import com.et.validation.validate.IpAddress;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.*;
@Data
@ToString
public class UserInfoReq {
@NotNull(message = "id is null")
private Long id;
@NotBlank(message = "username is null")
private String username;
@NotNull(message = "age is null")
@Min(value = 1, message = "min age is 1 ")
@Max(value = 200, message = "max age is 200")
private Integer age;
@Email(message = "email format limit")
private String email;
@IpAddress(message = "ip format limit")
private String ip;
}
Custom error capture
When a validation fails, we typically want to return a meaningful error message to the client. In order for the client to be able to display a useful error message, we should return a uniform data structure with the error message for each validation failure. All we’re doing here is reading the information about the check failure from the exception and translating them to ours ValidationErrorResponse
data structure. Please note@ControllerAdvice
annotation, which makes the exception handling mechanism for the above types of exceptions globally available to all controllers.
package com.et.validation.error;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
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;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
@ControllerAdvice
class ErrorHandlingControllerAdvice {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
ValidationErrorResponse onConstraintValidationException(
ConstraintViolationException e) {
ValidationErrorResponse error = new ValidationErrorResponse();
for (ConstraintViolation violation : e.getConstraintViolations()) {
error.getViolations().add(
new Violation(violation.getPropertyPath().toString(), violation.getMessage()));
}
return error;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
ValidationErrorResponse onMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
ValidationErrorResponse error = new ValidationErrorResponse();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
error.getViolations().add(
new Violation(fieldError.getField(), fieldError.getDefaultMessage()));
}
return error;
}
}
package com.et.validation.error;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class ValidationErrorResponse implements Serializable {
private List<Violation> violations = new ArrayList<>();
}
package com.et.validation.error;
import lombok.Data;
import java.io.Serializable;
@Data
public class Violation implements Serializable {
private final String fieldName;
private final String message;
public Violation(String fieldName, String message) {
this.fieldName = fieldName;
this.message = message;
}
}
Customize the validator
If the official checksum annotations available do not meet our needs, we can define a custom validator ourselves. Custom check summaries need to include the following elements:
- parameter
message
, specifies the property key in the ValidationMessages.properties file, which is used to parse the prompt message when validation fails, - parameter
groups
, which allows you to define when this check is triggered (we'll talk about grouping checks later) - parameter
payload
, allows you to define the Payload to be passed through this checksum (as this is a rarely used feature, we won't cover it in this tutorial) - One
@Constraint
Note, specifying the validation logic class that implements the ConstraintValidator interface.
The implementation of the validator is as follows:
package com.et.validation.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = IpAddressValidator.class)
@Documented
public @interface IpAddress {
String message() default "{IpAddress.invalid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
package com.et.validation.validate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class IpAddressValidator implements ConstraintValidator<IpAddress, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
Pattern pattern =
Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
Matcher matcher = pattern.matcher(value);
try {
if (!matcher.matches()) {
return false;
} else {
for (int i = 1; i <= 4; i++) {
int octet = Integer.valueOf(matcher.group(i));
if (octet > 255) {
return false;
}
}
return true;
}
} catch (Exception e) {
return false;
}
}
}
The above are just some of the key codes, all of which can be found in the repositories below
Code repositories
3. Testing
Start the Spring Boot application
Test the parameter verification interface
Access the http://127.0.0.1:8088/hello, and the following result is returned: