introduction
I don't know how you write the parameter verification of the controller layer in the usual business development process? Is there a direct judgment like the following?
public String add(UserVO userVO) {
if(userVO.getAge() == null){
return "年龄不能为空";
}
if(userVO.getAge() > 120){
return "年龄不能超过120";
}
if(userVO.getName().isEmpty()){
return "用户名不能为空";
}
// 省略一堆参数校验...
return "OK";
}
The business code hasn't been written yet, and a lot of judgments have been written for optical parameter verification. Although there is nothing wrong with writing this way, it gives people the feeling that it is not elegant and professional.
In fact, the Spring
framework has already encapsulated a set of verification components for us: validation. Its characteristics are easy to use and high degree of freedom. The next lesson represents the use of springboot-2.3.1.RELEASE
building a simple Web project, and I will explain step by step how to elegantly check the parameters during the development process.
1. Environment Setup
From the springboot-2.3
beginning, the verification package has been independent as a starter
component, so the following dependencies need to be introduced:
<!--校验组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
The springboot-2.3
previous version only needs to introduce web dependencies.
2. Small test
Parameter verification is very simple, first add a verification rule annotation on the field to be verified
public class UserVO {
@NotNull(message = "age 不能为空")
private Integer age;
}
Then controller
add in the method @Validated
and used to receive the error message BindingResult
, so there is the first version:
public String add1(@Validated UserVO userVO, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
if(!fieldErrors.isEmpty()){
return fieldErrors.get(0).getDefaultMessage();
}
return "OK";
}
Use the tool (postman) to request the interface. If the parameters do not meet the rules, the corresponding message
information will be returned:
age 不能为空
There are many built-in verification annotations, listed as follows:
annotation | Check function |
---|---|
@AssertFalse | Must be false |
@AssertTrue | Must be true |
@DecimalMax | Less than or equal to the given value |
@DecimalMin | Greater than or equal to the given value |
@Digits | The maximum number of integers and the maximum number of decimals can be set |
Check whether it conforms to Email format | |
@Future | Must be in the future |
@FutureOrPresent | Current or future time |
@Max | Max |
@Min | Minimum |
@Negative | Negative numbers (not including 0) |
@NegativeOrZero | Negative or 0 |
@NotBlank | Is not null and contains at least one non-whitespace character |
@NotEmpty | Not null and not empty |
@NotNull | Not null |
@Null | Is null |
@Past | Must be in the past |
@PastOrPresent | Must be in the past, including the present |
@PositiveOrZero | Positive or 0 |
@Size | Check the number of elements in the container |
3. Standard return value
After there are more parameters to be verified, we hope to return all verification failure information at one time to facilitate interface callers to adjust. This requires a unified return format. The common one is to encapsulate a result class.
public class ResultInfo<T>{
private Integer status;
private String message;
private T response;
// 省略其他代码...
}
Modify the controller
method, second edition:
public ResultInfo add2(@Validated UserVO userVO, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo<>().success(400,"请求参数错误",collect);
}
When this method is requested, all the error parameters are returned:
{
"status": 400,
"message": "请求参数错误",
"response": [
"年龄必须在[1,120]之间",
"bg 字段的整数位最多为3位,小数位最多为1位",
"name 不能为空",
"email 格式错误"
]
}
4. Global exception handling
Controller
If you write the BindingResult
information processing in each method, it is still very cumbersome to use. The check exception can be handled uniformly through global exception handling.
When we write a @validated
comment and don't write BindingResult
it, Spring will throw an exception. As a result, a global exception handling class can be written to uniformly handle this verification exception, thereby eliminating the need to repeat the code for organizing exception information.
The global exception handling class only needs to be marked on the class @RestControllerAdvice
, and @ExceptionHandler
annotations are used on the corresponding exception handling methods to specify which exception to handle.
@RestControllerAdvice
public class GlobalControllerAdvice {
private static final String BAD_REQUEST_MSG = "客户端请求参数错误";
// <1> 处理 form data方式调用接口校验失败抛出的异常
@ExceptionHandler(BindException.class)
public ResultInfo bindExceptionHandler(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
// <2> 处理 json 请求体调用接口校验失败抛出的异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultInfo methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
// <3> 处理单个参数校验失败抛出的异常
@ExceptionHandler(ConstraintViolationException.class)
public ResultInfo constraintViolationExceptionHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
List<String> collect = constraintViolations.stream()
.map(o -> o.getMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
}
In fact, in the global exception handling class, we can write multiple exception handling methods. The class representative summarized the exceptions that may be thrown during three parameter verification:
Use the form data method to call the interface, and the verification exception throws BindException
Use the json request body to call the interface, and the validation exception throws MethodArgumentNotValidException
Single parameter verification exception throws ConstraintViolationException
Note: Single parameter verification needs to add verification notes to the parameters and mark them on the class
@Validated
.
The global exception handling class can add various exceptions that need to be handled, such as adding a pair Exception.class
of exception handling. When all of ExceptionHandler
them cannot be handled, it records the exception information and returns a friendly prompt.
5. Group check
If you need to apply different verification rules in different scenarios for the same parameter, you need to use packet verification. For example: the newly registered user has not given a name, we allow the name
field to be empty, but it is not allowed to update the name to empty characters.
There are three steps to group verification:
Define a grouping class (or interface)
Add
groups
attribute specified grouping on verification annotationController
@Validated
Add grouping class to method annotation
public interface Update extends Default{
}
public class UserVO {
@NotBlank(message = "name 不能为空",groups = Update.class)
private String name;
// 省略其他代码...
}
@PostMapping("update")
public ResultInfo update(@Validated({Update.class}) UserVO userVO) {
return new ResultInfo().success(userVO);
}
Attentive students may have noticed that the custom Update
group interface inherits the Default
interface. The verification annotations (such as:) @NotBlank
and the @validated
default belong to the Default.class
grouping, which is javax.validation.groups.Default
explained in the annotations
/**
* Default Jakarta Bean Validation group.
* <p>
* Unless a list of groups is explicitly defined:
* <ul>
* <li>constraints belong to the {@code Default} group</li>
* <li>validation applies to the {@code Default} group</li>
* </ul>
* Most structural constraints should belong to the default group.
*
* @author Emmanuel Bernard
*/
public interface Default {
}
When writing a Update
packet interface, if inherited Default
, the following two writings are equivalent:
@Validated({Update.class})
@Validated({Update.class,Default.class})
Request the /update
interface to see that not only the name
fields are verified, but other fields that belong to the Default.class
group by default are also verified
{
"status": 400,
"message": "客户端请求参数错误",
"response": [
"name 不能为空",
"age 不能为空",
"email 不能为空"
]
}
If it is Update
not inherited Default
, @Validated({Update.class})
only Update.class
the parameter fields belonging to the group will be checked . After the modification, the interface is requested again to get the following results. You can see that other fields are not involved in the check:
{
"status": 400,
"message": "客户端请求参数错误",
"response": [
"name 不能为空"
]
}
6. Recursive verification
If an attribute of the OrderVO class is added to the UserVO class, and the attributes in the OrderVO also need to be verified, recursive verification is used, which @Valid
can be achieved by adding annotations to the corresponding properties (the same applies to collections)
The OrderVO class is as follows
public class OrderVO {
@NotNull
private Long id;
@NotBlank(message = "itemName 不能为空")
private String itemName;
// 省略其他代码...
}
Add an attribute of type OrderVO to the UserVO class
public class UserVO {
@NotBlank(message = "name 不能为空",groups = Update.class)
private String name;
//需要递归校验的OrderVO
@Valid
private OrderVO orderVO;
// 省略其他代码...
}
The call request verification is as follows:
7. Custom check
Spring's validation provides us with so many features, which can almost meet most of the parameter verification scenarios in daily development. However, a good framework must be easy to extend. With scalability, you can deal with more complex business scenarios. After all, in the development process, the only constant is the change itself .
Spring Validation allows users to customize the validation, the implementation is very simple, in two steps:
Custom check annotation
Write verifier class
The code is also very simple, you can understand it at a glance with the comments
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {HaveNoBlankValidator.class})// 标明由哪个类执行校验逻辑
public @interface HaveNoBlank {
// 校验出错时默认返回的消息
String message() default "字符串中不能含有空格";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* 同一个元素上指定多个该注解时使用
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
public class HaveNoBlankValidator implements ConstraintValidator<HaveNoBlank, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// null 不做检验
if (value == null) {
return true;
}
if (value.contains(" ")) {
// 校验失败
return false;
}
// 校验成功
return true;
}
}
The custom verification annotations are the same as the built-in annotations. Just add the corresponding annotations on the required fields, and students can verify by themselves
review
The above is the whole content of how to use Spring Validation to verify the parameters elegantly. The following focuses on the verification features mentioned in the article
Built-in a variety of common verification notes
Support single parameter verification
Combined with global exception handling to automatically assemble and verify exceptions
Packet check
Support recursive check
Custom check
There is no way, but the technique can be achieved; if there is no way, it ends with the technique
Welcome everyone to follow the Java Way public account
Good article, I am reading ❤️