1、簡介
在任何時候,當你要處理一個應用程序的業務邏輯,數據校驗是你必須要考慮和麪對的事情。應用程序必須通過某種手段來確保輸入進來的數據從語義上來講是正確的。在通常的情況下,應用程序是分層的,不同的層由不同的開發人員來完成。很多時候同樣的數據驗證邏輯會出現在不同的層,這樣就會導致代碼冗餘和一些管理的問題,比如說語義的一致性等。爲了避免這樣的情況發生,最好是將驗證邏輯與相應的域模型進行綁定。
Bean Validation 爲 JavaBean 驗證定義了相應的元數據模型和 API。缺省的元數據是 Java Annotations,通過使用 XML 可以對原有的元數據信息進行覆蓋和擴展。在應用程序中,通過使用 Bean Validation 或是你自己定義的 constraint,例如 @NotNull
, @Max
, @ZipCode
, 就可以確保數據模型(JavaBean)的正確性。constraint 可以附加到字段,getter 方法,類或者接口上面。對於一些特定的需求,用戶可以很容易的開發定製化的 constraint。Bean Validation 是一個運行時的數據驗證框架,在驗證之後驗證的錯誤信息會被馬上返回
2、Bean Validation 中的 constraint
1)、Bean Validation 中內置的 constraint
Constraint | 詳細信息 |
---|---|
@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) |
被註釋的元素必須符合指定的正則表達式 |
2)、 Hibernate Validator 附加的 constraint
Constraint | 詳細信息 |
---|---|
@Email |
被註釋的元素必須是電子郵箱地址 |
@Length |
被註釋的字符串的大小必須在指定的範圍內 |
@NotEmpty |
被註釋的字符串的必須非空 |
@Range |
被註釋的元素必須在合適的範圍內 |
3、簡單的使用註解校驗
1)、給Bean添加校驗註解:javax.validation.constraints,並定義自己的message提示
代碼如下
/**
* 排序
*/
@NotNull(message = "排序不能爲空")
@Min(value = 0,message = "排序必須大於等於0")
private Integer sort;
2)、在我們請求的方法的參數上加**@Valid**註解,開啓校驗功能
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
}
3)、給校驗的Bean後緊跟一個BindingResult,就可以獲取到校驗的結果
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult){
if(bindingResult.hasErrors()){
Map<String,String> map=new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
map.put(item.getField(),item.getDefaultMessage());
});
return R.error(400,"提交的參數不合法").put("data",map);
}else{
brandService.save(brand);
}
return R.ok();
}
4)、抽取參數Bean校驗提示代碼
因爲其他方法可能也會要對傳過來的數據進行校驗,我們把公共的部分抽取出來做統一處理
@Slf4j
@RestControllerAdvice(basePackages = "com.hsl.hslmall.product.controller")
public class HslMallExceptionControllerAadvice {
/**
* 數據校驗
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("數據校驗出現問題:{},異常類型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> map=new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
map.put(item.getField(),item.getDefaultMessage());
});
return R.error(BizCodeEnum.VALID_EXCEPRION.getCode(),BizCodeEnum.VALID_EXCEPRION.getMsg()).put("data",map);
}
/**
* 其他位置異常
* @param e
* @return
*/
@ExceptionHandler(value = Throwable.class)
public R handleVException(Throwable e){
log.error("數據校驗出現問題:{},異常類型:{}",e.getMessage(),e.getClass());
return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
}
抽取後,原來方法的代碼改爲如下
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
是不是隻關心邏輯代碼,不用重複的寫校驗的結果信息了?
4、分組校驗(多場景的複雜校驗)
1)、創建多場景下的分組接口
比如AddGroup,UpdateGroup 等一系列接口,只是作爲一個分組的標識,我們創建這兩個接口
然後在需要校驗的字段上加上:
@NotBlank(message = “品牌名不能爲空”,groups = {AddGroup.class,UpdateGroup.class})
2)、在Controller方法上啓用校驗分組
在controller方法上加上**@Validated({AddGroup.class})**
標註我這個方法的參數只校驗標註了AddGroup分組的字段,其他字段沒有標註AddGroup的,不生效
5、自定義校驗
1)、編寫一個自定義的校驗註解
- 比如下面代碼,定義了一個註解@ListValue,作用是判斷showStatus的值是否符合我們指定的值
/** * 顯示狀態[0-不顯示;1-顯示] */
@ListValue(value={0,1},groups = {AddGroup.class})
private Integer showStatus;
- 創建ListValue註解接口,可以仿照其他校驗的註解接口來寫
@Documented
//指定校驗器
@Constraint(validatedBy = {})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
//自定義默認配置消息
String message() default "{com.hsl.hslmall.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] value() default {};
}
- 在resources下創建 ValidationMessages.properties 文件,這個是固定寫法,因爲實現了其他校驗規則的默認消息就是獲取這個文件名中配置的內容的
com.hsl.hslmall.common.valid.ListValue.message=必須提交指定的值
2)、編寫一個自定義校驗器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
Set<Integer> set=new HashSet<>();
/**
* 初始化方法
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = constraintAnnotation.value();
for (int i : value) {
set.add(i);
}
}
/**
* 校驗是否成功
* @param value 字段指定的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
3)、關聯自定義的校驗器和自定義校驗註解
@Constraint(validatedBy = {ListValueConstraintValidator.class})
4)、引入自定義校驗的jar
<!--自定義校驗導入-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
參數校驗可以很好的控制頁面輸入的一些錯誤數據,當然我們頁面也是可以做校驗的,那麼頁面做一邊校驗,後臺做一遍校驗,這樣保存到數據庫中的數據是不是會更完整,更安全一些呢?