背景:
1.Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
2.Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。
3.我们将会在Spring Validation 的基础上在定向的去再次通过自定义注解的方式进行 “分装”成我们的工具类。
实现过程
1.pom.xml 添加 Spring Validation 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.4.4</version>
</dependency>
2.Controller 层Method 添加 @Validated
@RequestMapping(value = "valid", method = RequestMethod.POST)
public String valid(@RequestBody @Validated Person person) {
System.out.println(person);
return "success";
}
3.实体类中增加注解
@Data
public class Person {
@isEmail
private String email;
@isIdCard
private String idCard;
// @isContainChineseChar
private String entEnName;
@NotEmpty //Spring Validation 自带注解
private String entCNName;
@Valid
private EntInfo entInfo;
@Valid
private List<EntInfo> entInfoList;
@IdCard(message = "id错啦,快去检查")
private Integer id;
@DateType
private Date createTime;
4.统一异常处理
如果校验失败,会抛出BindException异常,在实际的开发中肯定是要统一处理下异常来返回一个友好的提示。所以对这个异常进行了处理。
@RestControllerAdvice
public class BindExceptionHandler {
@ExceptionHandler(BindException.class)
public String handleBindException(HttpServletRequest request, BindException exception) {
List<FieldError> allErrors = exception.getFieldErrors();
StringBuilder sb = new StringBuilder();
for (FieldError errorMessage : allErrors) {
sb.append(errorMessage.getField()).append(": ").append(errorMessage.getDefaultMessage());
}
System.out.println(sb.toString());
return sb.toString();
}
}
5.分组校验 (Spring Validation )自带功能
在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。
@isContainChineseChar(message = "该字符串中包含中文",groups = Save.class)
private String entEnName;
@NotEmpty(message = "内容不能为空",groups = Update.class) //Spring Validation 自带注解
private String entCNName;
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
@Validated注解上指定校验分组
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
6.嵌套校验/集合校验
实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!
@Valid
private EntInfo entInfo;
@Valid
private List<EntInfo> entInfoList;
添加@Valid注解
以上其实都是 Spring Validation 自己分装的。
我们需要做的就是踩在巨人的肩膀上,做自己要做的事儿
7.自定义注解
业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求。
简单举例身份证验证:
@Documented
@Constraint(validatedBy = isIdCard.NameValidationValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RUNTIME)
public @interface isIdCard {
String message() default "身份证内容为空或身份证内容格式错误!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class NameValidationValidator implements ConstraintValidator<isIdCard, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (isIdCard(value)){
return true;
}
String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
System.out.println("default message :" + defaultConstraintMessageTemplate);
//禁用默认提示信息
//context.disableDefaultConstraintViolation();
//设置提示语
//context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
return false;
}
/** 校验身份证号码 */
public static boolean isIdCard(String idCard) {
return StringUtil.isNotEmpty(idCard)
&& idCard.matches(
"[1-9]\\d{5}(18|19|20|(3\\d))\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]");
}
}
}
@Target (作用范围:方法,属性,构造方法等),
@Retention(生命范围:源代码,class,runtime)
实现ConstraintValidator接口编写约束校验器
这样我们就可以使用 @isIdCard 注解进行验证了。使用方式和 Spring Validation 中的注解使用是一样的。
8 编程式校验
其实在我们通过注解的方式进行校验外,我们也可以通过编程式调用校验。
9.快速失败(Fail Fast)
Spring Validation 默认会校验完所有字段,多个注解都要校验,然后才抛出异常。可以通过一些简单的配置,开启Fali Fast模式,一旦校验失败就立即返回
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}