使用protobuf时,对参数的校验很不方便,多是要手动编写一堆if-else的判断条件。介绍下使用javax validation来做参数校验,减少代码量、提供开发效率、定制统一的错误返回结果。
原生的校验注解有:
注解 | 作用 |
---|---|
@Size | 判断集合或字符的大小 |
@NotEmpty | 判断集合或字符不能为空 |
@Pattern | 判断正则表达式是否满足 |
@PastOrPresent | 判断日期类型是否小于等于某时 |
@Past | 判断日期类型是否小于某时 |
@FutureOrPresent | 判断日期类型是否大于等于某时 |
@Future | 判断日期类型是否大于某时 |
@DecimalMin | 判断数值类型的最小值 |
@DecimalMax | 判断数值类型的最大值 |
@Digits | 判断数值类型的最大位数和小数位数 |
@Min | 判断数值的最小值 |
@Max | 判断数值的最大值 |
@PositiveOrZero | 判断数值是否非负 |
@NegativeOrZero | 判断数值是否非正 |
@Negative | 判断数值是否是负数 |
@Positive | 判断数值是否是正数 |
@AssertTrue | 判断布尔类型是否为true |
@AssertFalse | 判断布尔类型是否为false |
判断字符串是否是正确的邮箱格式 | |
@NotBlank | 判断字符不能为空、不能含义空字符 |
@Null | 判断为null |
@NotNull | 判断不能为null |
注解中有message属性用于自定义校验错误的输出内容。
要想使用javax validation对controller层接收的参数对象进行校验,使用protobuf接口的难处理,所有建议转成javabean后对象,bean对象校验。
下面的例子介绍了的基本应用。
引用MapStruct文档(十二)——protobuf映射protobuf2和javabean转换的例子。
@NotNull
@Min(value = 2)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface Min2 {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
String message() default "格式不匹配";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
CaseMode value();
}
public enum CaseMode {
UPPER,
LOWER;
}
public interface Group1 {
}
public interface Group2 {
}
/**
* 验证字符类型的字段是否都是大写或小写。ConstraintValidator<A extends Annotation, T>中的T是要支持的属性类型。
*/
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
@Override
public void initialize(CheckCase constraintAnnotation) {
caseMode = constraintAnnotation.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean isValid;
if (value == null) {
return true;
}
if (caseMode == CaseMode.UPPER) {
isValid = value.equals(value.toUpperCase());
} else {
isValid = value.equals(value.toLowerCase());
}
if (!isValid) {
/*ConstraintValidatorContext通常用户自定义错误信息。
类似于spring中注入一个bean。在验证中现有的类无法实现某些功能,额外的导入其他支持要求的类。
这里要求可以在返回的错误中添加参数。但官方不建议使用ConstraintValidatorContext#buildConstraintViolationWithTemplate,
因为自定义消息模板是被直接传递到表达式语言引擎中的,可能会造成漏洞;建议使用HibernateConstraintValidatorContext;
Hibernate Validator可以进行处理转义,并且不会执行EL表达式。*/
HibernateConstraintValidatorContext hibernateConstraintValidatorContext =
context.unwrap(HibernateConstraintValidatorContext.class);
hibernateConstraintValidatorContext.disableDefaultConstraintViolation();
hibernateConstraintValidatorContext.addExpressionVariable("validatedValue", value)
.buildConstraintViolationWithTemplate("${validatedValue}" + "不满足" + caseMode.name())
.addConstraintViolation();
}
return isValid;
}
}
@Data
@GroupSequence({TestDTO.class, Group1.class, Group2.class})
public class TestDTO {
@Min2
private Integer id;
@CheckCase(value = CaseMode.UPPER)
private String name;
private Date createTime;
private TypeEnum downloadResourceTypeEnum;
@Valid
@ConvertGroup(from = Default.class, to = Group2.class)
private ItemDTO mainItem;
@AssertTrue(groups = Group1.class)
private Boolean disable;
@Digits(integer = 2, fraction = 1)
private BigDecimal prePrice;
private Map<String, String> kv;
private String oneString;
private Integer oneInt;
@Size(max = 5, min = 1, groups = Group2.class)
private List<ItemDTO> itemList;
private List<String> numberList;
private List<TypeEnum> typesList;
private List<Integer> nosList;
}
@Data
public class ItemDTO {
@Min(value = 100)
private Long itemId;
@DecimalMax(value = "350", message = " ${formatter.format('%1$.2f', validatedValue)} 大于 {value}", groups = Group2.class)
private Double price;
@NotNull(groups = Group2.class, message = "给我个值")
private Integer uint32Count;
private Long uint64Count;
private Integer sint32Count;
private Long sint64Count;
private Integer fixed32Count;
private Long fixed64Count;
private Integer sfixed32Count;
private Long sfixed64Count;
private byte[] type;
}
@Resource
private Validator validator;
Test.Builder builder = Test.newBuilder();
builder.setName("ss");
builder.setDisable(false);
builder.setPrePrice(1);
Item.Builder itemBuilder = Item.newBuilder();
itemBuilder.setItemId(1);
builder.setMainItem(itemBuilder);
TestDTO testDTO = testMapper.toDTO(builder.build());
Set<ConstraintViolation<TestDTO>> validate = validator.validate(testDTO);
System.out.println(validate);
结果
AssertTrue#groups定义的是校验组,校验组是个接口,用去区别不同字段在什么情况下校验;
@GroupSequence定义的是校验组顺序,前面组校验不通过后面的组就不会校验;不设置校验组,都是属于Default.class的默认组;指定类的@GroupSequence,要用类本身取代Default.class;
@ConvertGroup只能和@Valid连用,用于验证对象属性时,校验其中的那组;
@Min2是一个组合校验;
@CheckCase是自定义的校验。
Validation.buildDefaultValidatorFactory().getValidator().validate(T object, Class<?>… groups)就是校验的方法,object是被校验的对象,groups可以指定校验组。
message = " KaTeX parse error: Expected '}', got 'EOF' at end of input: …tter.format('%1.2f’, validatedValue)} 大于 {value}"中可以使用EL表达式。@NotNull上不用使用。