Java基础之数据校验Validation

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

JSR303/JSR349/JSR380

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

  • JSR 303:Bean Validation 1.0,伴随着JavaEE 6在2009年发布,Hibernate实现版本为4.3.1.Final
  • JSR 349:Bean Validation 1.1,伴随着JavaEE 7在2013年发布,Hibernate实现版本为5.1.1.Final
  • JSR 380:Bean Validation 2.0,伴随着JavaEE 8在2017年发布,Hibernate实现版本为6.0.1.Final

主流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另一个特性,便是其在Spring MVC模块中添加自动校验,并将校验信息封装进特定的类中。

JSR349

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

官网
GitHub地址

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

建议使用新版本:
在这里插入图片描述
新版本GAV:

<dependency>
	<groupId>jakarta.validation</groupId>
	<artifactId>jakarta.validation-api</artifactId>
	<version>3.1.0</version>
</dependency>

关于Jakarta,可参考Jakarta项目介绍

2018年发布Jakarta的第一个可用版本,最新版本是3.1.0
在这里插入图片描述
Jar目录结构如下:
在这里插入图片描述

注解

包括如下:

注解desc
@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),空格 和制表符等是blank,不是empty
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@NotNull限制必须不为null
@Null限制只能为null
@Past限制必须是一个过去的日期
@PastOrPresent限制必须是一个过去或者当前日期
@Pattern(value)限制必须符合指定的正则表达式
@Positive限制必须为一个正数
@PositiveOrZero限制必须为一个正数或者0
@Size(max,min)限制字符长度必须在min到max之间
@SupportedValidationTarget
@ValidateOnExecution
@ConvertGroup
@ExtractedValue
@UnwrapByDefault
@Constraint
@GroupSequence
@OverridesAttribute
@ReportAsSingleViolation
@Valid

Hibernate Validator

如果不使用validation-apijakarta.validation-api,也可考虑使用hibernate-validator。Hibernate Validator是Jakarta Bean Validation参考实现。

不要使用下面这个GAV:

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
</dependency>

在这里插入图片描述
推荐使用新的GAV:

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

包结构如下图所示:
在这里插入图片描述
实际上,hibernate-validator内部还是依赖jakarta.validation-api及其他各种三方依赖:
在这里插入图片描述
Hibernate Validator提供的未被标注为@Deprecated的校验注解:

注解备注
@CNPJbr包下面,
@CPF同上,
@TituloEleitoral同上,
@NIPpl包下面,
@PESEL同上,
@REGON同上,
@INNru包下面,
@DurationMaxtime包下面,
@DurationMin同上,
@CodePointLengthconstraints包下面,下面全部都是
@ConstraintComposition
@CreditCardNumber限定数据满足信用卡卡号规则
@Currency限定数据满足币种规则
@EAN
@ISBN限定数据满足ISBN规则
@Length
@LuhnCheck
@Mod10Check
@Mod11Check
@Normalized
@ParameterScriptAssert
@Range
@ScriptAssert
@UniqueElements
@URL限定数据满足URL规则

Spring Validation

Spring Validation全面支持JSR-303、JSR-349的标准,并封装LocalValidatorFactoryBean作为validator的实现,兼容Spring的validation体系和Hibernate的validation体系,也可以被开发者直接调用,代替上述的从工厂方法中获取的Hibernate validator。

使用Spring Boot,会触发Web模块的自动配置,LocalValidatorFactoryBean已经成为Validator的默认实现,使用时只需要自动注入即可。Maven引入如下依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

在这里插入图片描述

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

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

实例

使用校验注解

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

全局异常配置类:

@ControllerAdvice
public class ExceptionAdvice extends DefaultExceptionAdvice {

	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseBody
	public List<ErrMsg> exception(MethodArgumentNotValidException e) {
		BindingResult bindingResult = e.getBindingResult();
		List<ObjectError> allErrors = bindingResult.getAllErrors();
		List<ErrMsg> errMsgs = new ArrayList<>();
		allErrors.forEach(objectError -> {
			ErrMsg errMsg = new ErrMsg();
			FieldError fieldError = (FieldError) objectError;
			errMsg.setField(fieldError.getField());
			errMsg.setObjectName(fieldError.getObjectName());
			errMsg.setMessage(fieldError.getDefaultMessage());
			errMsgs.add(errMsg);
		});
		return errMsgs;
	}
}

实体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) {
}

分组校验

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

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;
	}
}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

johnny233

晚饭能不能加鸡腿就靠你了

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值