数据校验validation

概述

form表单传输到后端的数据,需要经过校验,前端的JS校验虽然可以涵盖大部分的校验职责,如生日格式,邮箱格式校验等。但是为了避免用户绕过浏览器,使用http/curl等工具直接向后端请求一些违法数据,造成安全事故,故服务端的数据校验显得更为重要。

JSR303/JSR349/JSR380

JSR303是专家组成员向JCP提交的第一版Bean Validation,即针对bean数据校验提出的一个规范,使用注解方式实现数据校验。后面有升级版本JSR349及JSR380。各个版本的规范对应关系如下:

JSR303伴随着JAVAEE 6在2009年发布,Hibernate实现版本4.3.1.Final,Apache BVal实现版本0.5
JSR349伴随着JAVAEE 7在2013年发布,Hibernate实现版本5.1.1.Final,Apache BVal实现版本1.1.2
JSR380伴随着JAVAEE 8在2017年发布,完全兼容低版本的JAVA SE,Hibernate实现版本6.0.1.Final,Apache BVal实现版本2.0.3(不太确定)

主流Bean Validation使用Hibernate的实现。

所以如果使用2.0规范,Hibernate必须选择6.0.1以上版本

JSR规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下,只提供规范不提供实现。

而hibernate validation是对这个规范的实践,提供相应的实现,并增加一些其他校验注解,如@Email,@Length,@Range等等,位于org.hibernate.validator.constraints包下。spring对hibernate validation进行二次封装,显示校验validated bean时,可以使用spring validation或hibernate validation,而spring validation另一个特性,便是其在springmvc模块中添加自动校验,并将校验信息封装进特定的类中。

JSR349

每一个注解都包含message字段,用于校验失败时作为提示信息,特殊的校验注解,如Pattern(正则校验),还可以自己添加正则表达式。

https://github.com/beanvalidation/beanvalidation-api

<dependency>
	<groupId>javax.validation</groupId>
	<artifactId>validation-api</artifactId>
	<version>2.0.1.Final</version>
</dependency>

和API组织结构几乎一模一样(没有一一去对比),建议使用新版本

Maven Repository: Search/Browse/Explore

https://mvnrepository.com/

使用搜索Maven Central Repository搜索jakarta.validation-api,得到如下:
https://search.maven.org/artifact/jakarta.validation/jakarta.validation-api
可知其官网GitHub

搜索可知,2018年才发布第一个可用版本,最新版本是3.0.0
在这里插入图片描述

<dependency>
	<groupId>jakarta.validation</groupId>
	<artifactId>jakarta.validation-api</artifactId>
	<version>3.0.0</version>
	<scope>compile</scope>
</dependency>

在这里插入图片描述

故而在此之前,一般选择使用

在这里插入图片描述

Hibernate Validator Engine
Hibernate’s Jakarta Bean Validation reference implementation.

<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>7.0.1.Final</version>
</dependency>

在这里插入图片描述
不要使用下面这个GAV:

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>7.0.1.Final</version>
</dependency>

实例

@Data
public class ErrorMsg {
    private String field;
    private String objectName;
    private String message;
}

全局异常配置类:

@ControllerAdvice
public class ExceptionAdvice extends DefaultExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public List<ErrorMsg> exception(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        List<ErrorMsg> errorMsgs = new ArrayList<>();

        allErrors.forEach(objectError -> {
            ErrorMsg errorMsg = new ErrorMsg();
            FieldError fieldError = (FieldError) objectError;
            errorMsg.setField(fieldError.getField());
            errorMsg.setObjectName(fieldError.getObjectName());
            errorMsg.setMessage(fieldError.getDefaultMessage());
            errorMsgs.add(errorMsg);
        });
        return errorMsgs;
    }

}

实体POJO:

import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

@Data
public class SetMealConfigVO {
    /**
     * 解读信息
     */
    @NotEmpty(message = "解读信息不能为空")
    private String unscrambleType;

    /**
     * 套餐有效时长
     */
    @NotNull(message = "套餐有效时长不能为空")
    @Min(value = 1, message = "不能为负数")
    private Integer useDays;
}
@PostMapping("/addOrEdit")
public Result<String> addOrEdit(@RequestBody @Valid SetMealConfigVO configVO, @LoginUser SysUser user) {
}

2.0.2和3.0.0版本提供的注解一模一样:

注解备注
@AssertFalse限制必须为false
@AssertTrue限制必须为true
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
@Future限制必须是一个将来日期
@FutureOrPresent限制必须是一个将来或当前日期
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Negative限制必须为一个负数
@NegativeOrZero限制必须为一个负数或者0
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@NotNull限制必须不为null
@Null限制只能为null
@Past限制必须是一个过去的日期
@PastOrPresent限制必须是一个过去或者当前日期
@Pattern(value)限制必须符合指定的正则表达式
@Positive限制必须为一个正数
@PositiveOrZero限制必须为一个正数或者0
@Size(max,min)限制字符长度必须在min到max之间

Hibernate Validator

在这里插入图片描述

Hibernate Validator提供的校验注解,未包括标注为@Deprecated的:

注解备注
@CNPJbr包下面,
@CPFbr包下面,
@TituloEleitoralbr包下面,
@NIPpl包下面,
@PESELpl包下面,
@REGONpl包下面,
@INNru包下面,
@DurationMaxtime包下面,
@DurationMintime包下面,
@CodePointLength
@ConstraintComposition
@CreditCardNumber
@Currency
@EAN
@ISBN
@Length
@LuhnCheck
@Mod10Check
@Mod11Check
@Normalized
@ParameterScriptAssert
@Range
@ScriptAssert
@UniqueElements
@URL

Spring validation

Spring validation全面支持JSR-303、JSR-349的标准,并封装LocalValidatorFactoryBean作为validator的实现,兼容spring的validation体系和hibernate的validation体系,也可以被开发者直接调用,代替上述的从工厂方法中获取的hibernate validator。使用springboot,会触发web模块的自动配置,LocalValidatorFactoryBean已经成为Validator的默认实现,使用时只需要自动注入即可。

参考下面分组校验的code,参数Foo前需要加上@Validated注解,表明需要Spring对其进行校验,而校验的信息会存放到其后的BindingResult中,必须相邻。如果有多个参数需要校验,形式如下:foo(@Validated Foo foo, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult);即一个校验类对应一个校验结果。

Spring validation不会在第一个错误发生后立即停止,而是继续试错,告诉所有的错误。

分组校验

如果同一个类,在不同的使用场景下有不同的校验规则,那么可以使用分组校验。实际需求,如未成年人是不能喝酒,如何校验?

Class Foo{
   @Min(value = 18, groups = {Adult.class})
   private Integer age;
   public interface Adult{}
   public interface Minor{}
}
@RequestMapping("/drink")
public String drink(@Validated({Foo.Adult.class}) Foo foo, BindingResult bindingResult) {
	if (bindingResult.hasErrors()) {
		for (FieldError item : bindingResult.getFieldErrors()) {
		}
		return "fail";
	}
	return "success";
}

自定义校验

作为示例,自定义校验注解@CannotHaveBlank,实现字符串不能包含空格的校验限制:

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
// 自定义注解中指定这个注解真正的验证者类
@Constraint(validatedBy = {CannotHaveBlankValidator.class})
public @interface CannotHaveBlank {
   // 默认错误消息
   String message() default "不能包含空格";
   // 分组
   Class<?>[] groups() default {};
   // 负载
   Class<? extends Payload>[] payload() default {};
   // 指定多个时使用
   @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
   @Retention(RUNTIME)
   @Documented
   @interface List {
       CannotHaveBlank[] value();
   }
}

接口ConstraintValidator:

public interface ConstraintValidator<A extends Annotation, T> {
	void initialize(A constraintAnnotation);// 初始化事件方法
	boolean isValid(T value, ConstraintValidatorContext context);// 判断是否合法
}

实现ConstraintValidator接口完成定制校验逻辑的类:

// 所有的验证者都需要实现ConstraintValidator接口
public class CannotHaveBlankValidator implements ConstraintValidator<CannotHaveBlank, String> {
	@Override
	public void initialize(CannotHaveBlank constraintAnnotation) {
	}
	@Override
	// ConstraintValidatorContext包含认证中所有的信息,
	// 获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作。
	public boolean isValid(String value, ConstraintValidatorContext context) {
	    if (value != null && value.contains(" ")) {
	        //获取默认提示信息
	        String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
	        System.out.println("default message :" + defaultConstraintMessageTemplate);
	        //禁用默认提示信息
	        context.disableDefaultConstraintViolation();
	        //设置提示语
	        context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
	        return false;
	    }
	    return true;
	}
}

参考

@valid与@validated

https://blog.csdn.net/gaojp008/article/details/80583301

https://blog.csdn.net/qq_37209293/article/details/85613319

SpringMVC中实现Bean Validation(JSR 303 JSR 349 JSR 380)

https://blog.csdn.net/u013815546/article/details/77248003

posted @ 2021-06-30 22:57  johnny233  阅读(38)  评论(0编辑  收藏  举报  来源