javax validation
使用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上不用使用。