文章目录
一、引入依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!--springboot 新版本需要validation启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 验证注解都在
javax.validation.constraints
这个包下 - 默认返回的异常信息存放在
ValidationMessages_zh_CN.properties
文件中
二、基本校验
1. 常用校验注解
注解 | 类型 | 说明 |
---|---|---|
@Null | Object | 被注释的元素必须为 null |
@NotNull | Object | 被注释的元素必须不为 null |
@AssertTrue | boolean/Boolean | 被注释的元素必须为 true |
@AssertFalse | boolean/Boolean | 被注释的元素必须为 false |
@Min(value) | BigDecimal/BigInteger/byte/short/int/long | 被注释数字必须大于等于指定的最小值 |
@Max(value) | BigDecimal/BigInteger/byte/short/int/long | 被注释数字必须小于等于指定的最大值 |
@Range(min=,max=) | BigDecimal/BigInteger/byte/short/int/long | @Min 和 @Max 注解的合并 |
@DecimalMin(value) | BigDecimal/BigInteger/byte/short/int/long | 被注释的数字必须大于等于指定的最小值 |
@DecimalMax(value) | BigDecimal/BigInteger/byte/short/int/long | 被注释的数字必须小于等于指定的最大值 |
@Size(max=, min=) | CharSequence/Collection/Map/Array | 被注释的集合、字符串的长度必须在指定的范围内 |
@Digits (integer, fraction) | BigDecimal/BigInteger/byte/short/int/long | 被注释的元素必须是一个数字,且满足 integer 参数表示整数位数最大值,fraction 表示小数位数的最大值 |
@Past | Date/Calendar | 被注释的日期必须是一个过去的日期 |
@Future | Date/Calendar | 被注释的日期必须是一个将来的日期 |
@Pattern(regex=,flag=) | String | 被注释的字符串必须符合指定的正则表达式 |
@NotBlank | String | 验证字符串非null,且长度必须大于0 |
@Length(min=,max=) | String | 被注释的字符串的长度必须在指定的范围内 |
@NotEmpty | string/collection/map/array | 被注释的字符串或集合不为空 |
String | 被注释的字符串必须是电子邮箱地址 |
2. 自动校验
1)定义一个实体类
public class Student {
@Min(1)
private Integer id;
@NotBlank
private String name;
@Range(min = 0, max = 200)
private Integer age;
@Min(1)
@Digits(fraction = 10, integer = 2)
private BigDecimal money;
}
2)在请求时加上注解 @Valid
@PostMapping(value = "/createStudent")
public R save(@Valid @RequestBody Student student){
}
3. 代码中获取校验结果
加上 @valid
后台验证,在验证的参数后加 BindingResult
就可以获取验证结果,不获取结果会抛出 MethodArgumentNotValidException
异常
@PostMapping(value = "/createStudent")
public R save(@Valid @RequestBody Student student, BindingResult result){
if (result.hasErrors()) {
Map<String , String > map = new HashMap<>();
result.getFieldErrors().forEach(item ->{
String msg = item.getDefaultMessage();
String field = item.getField();
map.put(field, msg);
});
return R.error(400, "提交的数据不合法").put("data", map);
}
studentService.save(student);
return R.ok();
}
三、嵌套校验
当实体类的字段为实体类或者为 List<Object>
时,要使用嵌套校验才可以校验内部的实体类, 要在字段上再加 @Valid
注解
@Data
public class AdInfo {
/**
* 区域统计数据
*/
@Valid
@NotNull(message = "arrival不能为空")
private Arrival arrival;
/**
* 客户群统计数据
*/
@Valid
@NotNull(message = "departure不能为空")
private List<Departure> departure;
}
四、分组校验
-
新建两个接口
public interface AddGroup { } public interface UpdateGroup { }
-
使用
@Validated
注解来指定使用哪个分组的校验规则,如果不指定,则只会校验没有指定分组规则,所有有group
参数的规则都不会校验/** * 保存 */ @RequestMapping("/save") //@RequiresPermissions("product:brand:save") public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){ brandService.save(brand); return R.ok(); } /** * 修改 */ @RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){ brandService.updateById(brand); return R.ok(); }
-
通过
groups
参数给字段上的注解分组,可以指定多个@Data @TableName("pms_brand") public class BrandEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 品牌id */ @NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class}) @Null(message = "新增不能指定id", groups = {AddGroup.class}) @TableId private Long brandId; /** * 品牌名 */ @NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class}) private String name; /** * 品牌logo地址 */ @URL(message = "logo必须是合法的URL地址", groups = {AddGroup.class, UpdateGroup.class}) @NotEmpty(groups = {AddGroup.class}) private String logo; /** * 介绍 */ private String descript; /** * 显示状态[0-不显示;1-显示] */ @ListValue(vals = {0, 1}, groups = {AddGroup.class}) private Integer showStatus; /** * 检索首字母 */ @Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class}) @NotEmpty(groups = {AddGroup.class}) private String firstLetter; /** * 排序 */ @Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class}) @NotNull(groups = {AddGroup.class}) private Integer sort; }
五、单属性自定义校验规则
1. 功能:只能取枚举的值
1)定义自定义校验器,实现 ConstraintValidator
接口
- 第一个泛型:注解
- 第二个泛型:修饰的字段类型
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> { private Set<Integer> set = new HashSet<>(); /** * 初始化方法,一般用于将自定义注解中的参数缓存 * @param constraintAnnotation 参数是自定义的注解 */ @Override public void initialize(ListValue constraintAnnotation) { int[] vals = constraintAnnotation.vals(); for (int val : vals) { set.add(val); } } /** * * @param value 传过来的参数 * @param context * @return true 校验通过 */ @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { return set.contains(value); } }
2)定义注解
- 通过
@Constraint(validatedBy = {ListValueConstraintValidator.class})
来指定上一步定义的校验器,可以指定多个校验器 - 注解必须有三个参数,
message
,groups
,payload
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListValue {
//这里指定 ValidationMessages.properties 文件中的键,一般是以注解的类路径来命名
//当校验失败时默认返回的消息
String message() default "{com.atguigu.common.valid.ListValue.message}";
//分组校验时使用
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}
3)指定默认的错误提示 message
,在 resource
中新建 ValidationMessages.properties
文件
com.atguigu.common.valid.ListValue.message="参数校验错误"
- 可能出现编码错误,在 File > Settings > Editor > File Encodings,添加 ValidationMessages.properties,指定编码为UTF-8,并勾选Transparent native-to-ascii conversion,不行就重新新建该文件。参考这篇博客
4)修饰字段
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
六、多属性自定义联合校验规则
1. 功能:不同优惠券类型校验不同参数
1)实体类中自定义方法,将所有关联校验的字段都统一返回
public class CouponTmplDto{
/**
* 优惠券模板类型(0 满减券,1折扣券)
*/
@NotNull
private Integer couponTmplType;
/**
* 满多少钱,规则类型为“满减券”时必填
*/
private BigDecimal fullMoney;
/**
* 减多少钱,规则类型为”满减券“时必填
*/
private BigDecimal reduceMoney;
/**
* 折扣率,规则类型为“折扣券”时必填
*/
private BigDecimal discountRate;
/**
* 注意:返回的方法要以 get 开头
*
* @return 所有所有要关联校验的参数
*/
@JsonIgnore
@CouponTmplTypeAnno(message = "优惠券类型校验失败")
public Map<String, Object> getCouponTmplTypeValidator() {
Map<String, Object> map = new HashMap();
map.put("couponTmplType", couponTmplType);
map.put("fullMoney", fullMoney);
map.put("reduceMoney", reduceMoney);
map.put("discountRate", discountRate);
return map;
}
}
2)自定义校验注解
@Documented
@Constraint(validatedBy = {CouponTmplTypeValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RUNTIME)
public @interface CouponTmplTypeAnno {
String message() default "优惠券模板类型及金额参数校验失败";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
3)自定义校验器
import cn.hutool.core.util.BooleanUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;
import java.util.Map;
public class CouponTmplTypeValidator implements ConstraintValidator<CouponTmplTypeAnno, Map<String, Object>> {
/**
* 自定义校验器初始化
*
* @param constraintAnnotation
*/
@Override
public void initialize(CouponTmplTypeAnno constraintAnnotation) {
//注解没有参数,不需要初始化
}
/**
* 校验方法
*
* @param map 注解在方法上时,该值是方法返回值;注解在字段上时,该值是字段值
* @param context
* @return true时校验通过,false时校验不通过
*/
@Override
public boolean isValid(Map<String, Object> map, ConstraintValidatorContext context) {
Integer couponTmplType = (Integer) map.get("couponTmplType");
BigDecimal fullMoney = (BigDecimal) map.get("fullMoney");
BigDecimal reduceMoney = (BigDecimal) map.get("reduceMoney");
BigDecimal discountRate = (BigDecimal) map.get("discountRate");
switch (CouponTmplTypeEnum.getByType(couponTmplType)) {
case FULL_REDUCT:
//满减券 满a减b a>b
return BooleanUtil.and(fullMoney != null, reduceMoney != null, fullMoney.compareTo(reduceMoney) > 0);
case DISCOUNT:
//折扣券 1位小数
return discountRate != null && discountRate.scale() <= CommonConstant.ONE;
default:
return false;
}
}
}
2. 通用多属性联合校验注解
如果多属性联合校验的条件不通用,每次校验都要写校验器、注解会很麻烦,这里使用匿名内部类的方式,实现每次只需要写校验逻辑即可,不用再定义校验器和注解
1)通用自定义注解:
@Documented
@Constraint(validatedBy = {CustomValidator.class})
@Target({ElementType.METHOD})
@Retention(RUNTIME)
public @interface CustomValidateAnno {
@AliasFor("message")
String value() default "{com.snbc.coupon.activity.annotation.CustomValidateAnno.message}";
/**
* Message string.
*
* @return the string
*/
@AliasFor("value")
String message() default "{com.snbc.coupon.activity.annotation.CustomValidateAnno.message}";
/**
* Groups class [ ].
*
* @return the class [ ]
*/
Class<?>[] groups() default {};
/**
* Payload class [ ].
*
* @return the class [ ]
*/
Class<? extends Payload>[] payload() default {};
}
2)通用校验器:
public class CustomValidator implements ConstraintValidator<CustomValidateAnno, BooleanSupplier> {
/**
* 自定义校验器初始化
*
* @param constraintAnnotation annotation instance for a given constraint declaration
*/
@Override
public void initialize(CustomValidateAnno constraintAnnotation) {
//不需要初始化
}
@Override
public boolean isValid(BooleanSupplier supplier, ConstraintValidatorContext context) {
//这里使用匿名内部类,实现校验器和注解通用
return supplier.getAsBoolean();
}
}
3)使用注解:(优化上一节中优惠券的校验规则)
public class CouponTmplDto {
/**
* 优惠券模板类型(0 满减券,1折扣券)
*/
@NotNull
private Integer couponTmplType;
/**
* 满多少钱,规则类型为“满减券”时必填
*/
private BigDecimal fullMoney;
/**
* 减多少钱,规则类型为”满减券“时必填
*/
private BigDecimal reduceMoney;
/**
* 折扣率,规则类型为“折扣券”时必填
*/
private BigDecimal discountRate;
/**
* 注意:返回的方法要以 get 开头
*
* @return 校验逻辑的匿名内部类, 校验通过(true), 校验不通过(false)
*/
@CustomValidateAnno
public BooleanSupplier getCouponTmplTypeValidateResult() {
return () -> {
switch (CouponTmplTypeEnum.getByType(couponTmplType)) {
case FULL_REDUCT:
//满减券 满a减b a>b
return BooleanUtil.and(fullMoney != null, reduceMoney != null, fullMoney.compareTo(reduceMoney) > 0);
case DISCOUNT:
//折扣券 1位小数
return discountRate != null && discountRate.scale() <= CommonConstant.ONE;
default:
return false;
}
};
}
/**
* 注意:返回的方法要以 get 开头
*
* @return 校验逻辑的匿名内部类, 校验通过(true), 校验不通过(false)
*/
@CustomValidateAnno("校验失败时提示消息")
public BooleanSupplier getOtherValidateResult() {
return () -> {
System.out.println("写校验方法");
// true 校验通过
return true;
};
}
}
七、手动校验
手动校验 registerInfo 这个对象是否符合规范
Set<ConstraintViolation<RegisterInfo>> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(registerInfo);