暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot - Bean validation 参数校验

咸鱼很咸 2021-08-18
724

前言

后台开发中对参数的校验是不可缺少的一个环节,为了解决如何优雅的对参数进行校验?


  • JSR303(Java Specification Requests)应运而生,JSR303 是JavaBean参数校验的标准。

  • Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。

  • Hibernate validator 5 是 Bean Validation 1.1的实现。


常见注解

  • Bean Validation中定义的注解:

注解详细信息
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式


  • Hibernate validator 在JSR303的基础上对校验注解进行了扩展,扩展注解如下:

注解详细信息
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range被注释的元素必须在合适的范围内


参数校验的应用

依赖

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


简单的参数校验示例

  • 要想开启参数校验,需要在类上标注@Validated注解

  • 控制器

 @PostMapping(value = "/test")
public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
return evt;
}


  • 实体验证

@Getter
@Builder
public class ValidOneEvt {

@NotEmpty(message = "名称不能为空")
private String name;

private String sex;

}


  • 测试结果


级联校验

  • 级联校验需要在校验的实体上添加@Valid。

  • 控制器

@PostMapping(value = "/test")
public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
return evt;
}


  • 实体验证

@Getter
@Builder
public class ValidOneEvt {

@NotEmpty(message = "名称不能为空")
private String name;

private String sex;

@Valid
private ValidTwoEvt validTwoEvt;

}


  • 级联实体

@Getter
@Setter
public class ValidTwoEvt {

@Length(min = 2)
private String name;

}


  • 请求示例


  • 校验结果


@Validated 与 @Valid

两者具有相似性
注解地方

  • @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上。

  • @Validated:可以用在类型、方法和方法参数上,不能用在成员属性(字段)上。

@Validated和@Valid在级联验证功能上的区别

  • @Valid:用在方法入参上无法单独提供级联验证功能。能够用在成员属性(字段)上,提示验证框架进行级联验证。

  • @Validated:用在方法入参上无法单独提供级联验证功能。不能用在成员属性(字段)上,也无法提示框架进行级联验证。能配合级联验证注解@Valid进行级联验证。

  • 总结:通常使用@Validated, 级联验证使用@Valid。


自定义校验注解

自定义校验注解用于基础校验注解不能满足业务需求。


  • 自定义效验注解验证密码是否相等

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
* @Description 自定义参数校验注解
* @Documented 注解中的注释加入文档
* @Retention 注解保留阶段 RetentionPolicy.RUNTIME 运行阶段
* @Target 作用目标,注解的使用范围 TYPE:用于描述类、接口(包括注解类型) 或enum声明
* @Constraint 将注解和注解关联类关联到一起
* @author coisini
* @date Aug 10, 2021
* @Version 1.0
*/

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEquals {

int min() default 4;

int max() default 6;

String message() default "passwords are not equal";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}


  • 自定义校验注解关联类

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
* @Description 自定义校验注解关联类
* ConstraintValidator的第一个参数:注解的类型
* ConstraintValidator的第二个参数:自定义注解修饰的目标的类型
* @author coisini
* @date Aug 10, 2021
* @Version 1.0
*/

public class PasswordValidator implements ConstraintValidator<PasswordEquals, ValidEvt> {

private int min;
private int max;

/**
* 初始化获取注解参数
* @param constraintAnnotation
*/

@Override
public void initialize(PasswordEquals constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}

/**
* 校验参数
* @param value
* @param context
* @return
*/

@Override
public boolean isValid(ValidEvt value, ConstraintValidatorContext context) {
String password1 = value.getPassword1();
String password2 = value.getPassword2();

return password1.equals(password2) && this.validLength(password1, password2);
}

/**
* 校验密码长度
* @param password1
* @param password2
* @return
*/

private boolean validLength(String password1, String password2) {
return password1.length() > min && password1.length() < max
&& password2.length() > min && password2.length() < max;
}

}


  • 自定义注解校验类

/**
* @Description 自定义注解校验类
* @author coisini
* @date Aug 10, 2021
* @Version 1.0
*/

@Getter
@Builder
@PasswordEquals(min = 1, message = "Incorrect password length or passwords are not equal")
public class ValidEvt {

private String password1;
private String password2;

}


  • 参数验证异常统一处理

@ControllerAdvice
public class GlobalExceptionAdvice {
/**
* 参数校验异常处理器
* @return
*/

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public UnifyMessage handleBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
String method = request.getMethod();
String requestUrl = request.getRequestURI();
System.out.println(e);

List<ObjectError> errors = e.getBindingResult().getAllErrors();
String message = formatAllErrorMessages(errors);

return new UnifyMessage(10001, message,method + " " + requestUrl);
}

/**
* 自定义注解校验异常处理器
* @param req
* @param e
* @return
*/

@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public UnifyMessage handleConstrainException(HttpServletRequest req, ConstraintViolationException e){
String method = req.getMethod();
String requestUrl = req.getRequestURI();
String message = e.getMessage();
return new UnifyMessage(10001, message,method + " " + requestUrl);
}

/**
* 异常消息拼接
* @param errors
* @return
*/

private String formatAllErrorMessages(List<ObjectError> errors){
StringBuffer errorMsg = new StringBuffer();
errors.forEach(error ->
errorMsg.append(error.getDefaultMessage()).append(";")
);
return errorMsg.toString();
}
}


  • 统一消息返回

/**
* @Description 统一消息返回
* @author coisini
* @date Aug 9, 2021
* @Version 1.0
*/

public class UnifyMessage {

private int code;
private String message;
private String requestUrl;

public int getCode() {
return code;
}

public String getMessage() {
return message;
}

public String getRequestUrl() {
return requestUrl;
}

public UnifyMessage(int code, String message, String requestUrl) {
this.code = code;
this.message = message;
this.requestUrl = requestUrl;
}

}


  • 测试类

@PostMapping(value = "/test1")
public ValidEvt test1(@RequestBody @Validated ValidEvt evt){
return evt;
}


  • 测试结果



END
文章转载自咸鱼很咸,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论