统一校验
此文代码托管地址: https://gitee.com/ZomiCC/code/tree/master/validate
我们平时都会碰到很多通用校验的场景:比如字段非空校验、字段长度校验等等。如下所示:
- @Null:被注释的元素必须为null
- @NotNull:被注释的元素不能为null
- @AssertTrue:该字段只能为true
- @AssertFalse:该字段的值只能为false
- @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- @DecimalMin(“value”):被注释的元素必须是一个数字,验证小数的最小值
- @DecimalMax(“value”):被注释的元素必须是一个数字,验证小数的最大值
- @Size(max,min):查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
- @Past:被注释的元素必须是一个过去的日期
- @Future:被注释的元素必须是一个将来的日期
- @Pattern(regexp = “[abc]”):被注释的元素必须符合指定的正则表达式。
- @Email:被注释的元素必须是电子邮件地址
- @Length(max=5,min=1,message=“长度在1~5”):检查所属的字段的长度是否在min和max之间,只能用于字符串
- @NotEmpty:被注释的字符串必须非空
- @Range:被注释的元素必须在合适的范围内
- @NotBlank:不能为空,检查时会将空格忽略
- @NotEmpty:不能为空,这里的空是指空字符串
具体是如何实现的呢,话不多说,直接看使用方式。
两种校验方式
一、@Validated + BindingResult controller校验
请求实体与BindingResult一一对应。
依赖的注解:
展开
<!-- valid校验 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- 想要BindResult生效,这个依赖一定要有 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
点击查看代码
@Data
@ToString
public class ReqDTO {
@NotBlank(message = "param1不能为空")
private String param1;
@NotBlank(message = "param2不能为空")
private String param2;
@NotBlank(message = "param3不能为空")
private String param3;
}
点击查看代码
@PostMapping("/param1")
public String param1(@Validated @RequestBody ReqDTO reqDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
assert fieldError != null;
return fieldError.getDefaultMessage();
}
return "success";
}
响应
二、自定义Validator校验工具类
不局限于controller层,在任何地方都可以使用。
工具类:
点击查看代码
package com.example.validate.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Component
public class ParamCheckUtil {
private static Validator validator;
@Autowired
private Validator injectValidator;
@PostConstruct
public void preInit() {
validator = injectValidator;
}
public static <T> String checkByGroupsGetOneMessage(T t, Class<?>... groups) {
if (t == null) {
return null;
}
Set<ConstraintViolation<T>> validate = validator.validate(t, groups);
if (validate.size() == 0) {
return null;
}
return validate.stream().findFirst().get().getMessage();
}
public static <T> String checkByGroupsGetAllMessage(T t, Class<?>... groups) {
if (t == null) {
return null;
}
Set<ConstraintViolation<T>> validate = validator.validate(t, groups);
if (validate.size() == 0) {
return null;
}
List<String> msgList = new ArrayList<>();
validate.forEach(cv -> msgList.add(cv.getMessage()));
return String.join(";", msgList);
}
}
点击查看代码
@PostMapping("/param2")
public String param2(@RequestBody ReqDTO reqDTO) {
String message = ParamCheckUtil.checkByGroupsGetAllMessage(reqDTO);
if (message != null) {
return message;
}
return "success";
}
其他扩展校验
一、分组校验
当一个实体类应用于多个场景的请求入参时,对入参要求又不一样,我们没必要每次都新建一个冗余的实体类。那怎么办呢,可以给校验属性添加上分组。默认分组是Default。
实体类:
点击查看代码
@Data
@ToString
public class GroupReqDTO {
@NotBlank(message = "param1不能为空")
private String param1;
@NotBlank(message = "param2不能为空", groups = A.class)
private String param2;
@NotBlank(message = "param3不能为空", groups = B.class)
private String param3;
public interface A {}
public interface B {}
}
点击查看代码
@PostMapping("/param4")
public String param4(@Validated({GroupReqDTO.A.class}) @RequestBody GroupReqDTO reqDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
assert fieldError != null;
return fieldError.getDefaultMessage();
}
return "success";
}
二、嵌套校验
当存在嵌套引用对象时,可以使用嵌套校验。嵌套类型需要使用@Valid注解标识。
实体类:
点击查看代码
@Data
public class NestedReqDTO {
@NotBlank(message = "param1不能为空")
private String param1;
@NotBlank(message = "param2不能为空")
private String param2;
@NotBlank(message = "param3不能为空")
private String param3;
@Valid
@NotNull(message = "param4不能为空")
private OtherEntity param4;
@Data
class OtherEntity {
@NotBlank(message = "属性1不能为空")
private String property1;
}
}
点击查看代码
@PostMapping("/param5")
public String param5(@Validated @RequestBody NestedReqDTO reqDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
assert fieldError != null;
return fieldError.getDefaultMessage();
}
return "success";
}
三、复杂关联校验
借助DefaultGroupSequenceProvider,可以实现属性间依赖关系的复杂校验。具体实现参考如下代码:
实体类:
点击查看代码
@Data
@ToString
@GroupSequenceProvider(MyGroupSequenceProvider.class)
public class MutiReqDTO {
@NotBlank(message = "param1不能为空")
private String param1;
@NotBlank(message = "param2不能为空")
private String param2;
@NotBlank(message = "当param2为1时,param3不能为空", groups = Param2.class)
private String param3;
public interface Param2 {}
}
分组配置器:
点击查看代码
public class MyGroupSequenceProvider implements DefaultGroupSequenceProvider<MutiReqDTO> {
@Override
public List<Class<?>> getValidationGroups(MutiReqDTO mutiReqDTO) {
List<Class<?>> classes = new ArrayList<>();
classes.add(MutiReqDTO.class);
if (mutiReqDTO == null) {
return classes;
}
// 如果param2参数为“1”,则需要校验param3
if ("1".equals(mutiReqDTO.getParam2())) {
classes.add(MutiReqDTO.Param2.class);
}
return classes;
}
}
controller测试:
点击查看代码
@PostMapping("/param3")
public String param3(@RequestBody MutiReqDTO reqDTO) {
String message = ParamCheckUtil.checkByGroupsGetAllMessage(reqDTO);
if (message != null) {
return message;
}
return "success";
}
验证效果:
高级校验(自定义注解校验)
一、自定义注解校验
有时候javax.validation.constraints包中提供的注解不够我们使用,我们也可以自定义注解。分三步:
- 自定义校验注解
- 实现ConstraintValidator<A,T>校验器
- 将自定义校验注解与校验器绑定
- 应用:使用注解修饰校验属性
下面我们通过常用的枚举类型校验进行举例说明(详细代码参考:EnumValue和AgeValue自定义注解举例):
自定义注解
package com.example.validate.annote;
import com.example.validate.validate.EnumValueValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {
String message() default "请输入指定值";
int[] ints() default {};
String[] strings() default {};
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义校验器
package com.example.validate.validate;
import com.example.validate.annote.EnumValue;
import org.springframework.util.ObjectUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
private int[] intValues;
private String[] stringValues;
/**
* 初始化
*/
@Override
public void initialize(EnumValue constraintAnnotation) {
this.intValues = constraintAnnotation.ints();
this.stringValues = constraintAnnotation.strings();
}
/**
* 校验逻辑
*/
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (ObjectUtils.isEmpty(value)) {
return true; //空值不校验
}
if (value instanceof String) {
return Arrays.asList(stringValues).contains(value);
}
return Arrays.asList(intValues).contains(value);
}
}
- 注解与校验器绑定
- 应用
实体类
package com.example.validate.dto;
import com.example.validate.annote.AgeValue;
import com.example.validate.annote.EnumValue;
import lombok.Data;
@Data
public class ValidatorEntity {
@EnumValue(strings = {"1", "2"}, message = "性别取值必须是[1,2]")
private String sex;
@AgeValue(value = 18, message = "年龄必须大于18")
private int age;
}
controller
@RestController
public class ValidatorController {
@PostMapping("/validate")
public String validate(@RequestBody ValidatorEntity request) {
String message = ParamCheckUtil.checkByGroupsGetAllMessage(request);
return message == null ? "校验通过" : "校验不通过" + message;
}
}
There are two things to do in a day: a happy thing and a difficult one.