如何使用Bean Validation 解決業務中參數校驗
前言
在開發中經常需要寫一些字段校驗的代碼,比如字段非空,字段長度限制,郵箱格式驗證等等,寫這些與業務邏輯關系不大的代碼個人感覺有點麻煩:
驗證代碼繁瑣,重復勞動
方法內代碼顯得冗長
每次要看哪些參數驗證是否完整,需要去翻閱驗證邏輯代碼
敘述
Bean Validation是一個通過配置註解來驗證參數的框架,它包含兩部分Bean Validation API和Hibernate Validator。
Bean Validation API是Java定義的一個驗證參數的規范。
Hibernate Validator是Bean Validation API的一個實現。
@Valid和Validated的比較
Spring Validation驗證框架對參數的驗證機制提供瞭@Validated(Spring’s JSR-303規范,是標準JSR-303的一個變種),javax提供瞭@Valid(標準JSR-303規范),配合BindingResult可以直接提供參數驗證結果。
@Valid : 沒有分組功能,可以用在方法、構造函數、方法參數和成員屬性(field)上,如果一個待驗證的pojo類,其中還包含瞭待驗證的對象,需要在待驗證對象上註解@valid,才能驗證待驗證對象中的成員屬性
@Validated :提供分組功能,可以在入參驗證時,根據不同的分組采用不同的驗證機制,用在類型、方法和方法參數上。但不能用於成員屬性(field)。
兩者都可以用在方法入參上,但都無法單獨提供嵌套驗證功能,都能配合嵌套驗證註解@Valid進行嵌套驗證。
嵌套驗證示例:
public class ClassRoom{ @NotNull String name; @Valid // 嵌套校驗,校驗參數內部的屬性 @NotNull Student student; }
@GetMapping("/room") // 此處可使用 @Valid 或 @Validated, 將會進行嵌套校驗 public String validator(@Validated ClassRoom classRoom, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
BindingResult 的使用
BindingResult必須跟在被校驗參數之後,若被校驗參數之後沒有BindingResult對象,將會拋出BindException。
@GetMapping("/room") public String validator(@Validated ClassRoom classRoom, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
不要使用 BindingResult 接收String等簡單對象的錯誤信息。簡單對象校驗失敗,會拋出 ConstraintViolationException。主要就是接不著,你要寫也算是沒關系…
// ❌ 錯誤用法,也沒有特別的錯,隻是 result 是接不到值。 @GetMapping("/room") @Validated // 啟用校驗 public String validator(@NotNull String name, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
修改校驗失敗的提示信息
可以通過各個校驗註解的message屬性設置更友好的提示信息。
public class ClassRoom{ @NotNull(message = "Classroom name must not be null") String name; @Valid @NotNull Student student; }
@GetMapping("/room") @Validated public String validator(ClassRoom classRoom, BindingResult result, @NotNull(message = "姓名不能為空") String name) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
message屬性配置國際化的消息也可以的,message中填寫國際化消息的code,在拋出異常時根據code處理一下就好瞭。
@GetMapping("/room") @Validated public String validator(@NotNull(message = "demo.message.notnull") String name) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
// message_zh_CN.properties demo.message.notnull=xxx消息不能為空 // message_en_US.properties demo.message.notnull=xxx message must no be null
hibernate-validator 的使用
1.引入pom
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.1.Final</version> </dependency>
2.dto入參對象屬性加入註解
@Data public class UserModel implements Serializable { private String id; @NotBlank(message = "用戶名不能為空") private String name; @NotNull(message = "性別不能不填") private Byte gender; @NotNull(message = "年齡不能不填") @Min(value = 0,message = "年齡必須大於0歲") @Max(value = 150,message = "年齡必須小於150歲") private Integer age; @NotBlank(message = "手機號不能不填") private String telphone; private String registerMode; private String thirdPartyId; private String encrptPassward; }
方法一:3.controller方法入參加入校驗(@Validated )
@GetMapping("/getUser") public String validator(@Validated UserModel userModel , BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
方法二:3.自定義封裝ValidatorImpl類
@Component public class ValidatorImpl implements InitializingBean{ private Validator validator; //實現校驗方法並返回校驗結果 public ValidationResult validate(Object bean){ final ValidationResult result=new ValidationResult(); Set<ConstraintViolation<Object>> validate = validator.validate(bean); if (validate.size()>0) { result.setHasError(true); validate.forEach(constraintViolation->{ String errMsg=constraintViolation.getMessage(); String propertyName=constraintViolation.getPropertyPath().toString(); result.getErrorMsgMap().put(propertyName,errMsg); }); } return result; } @Override public void afterPropertiesSet() throws Exception { this.validator= Validation.buildDefaultValidatorFactory().getValidator(); } }
方法二:4.自定義封裝ValidationResult 類
public class ValidationResult { public boolean hasError=false; private Map<String,String> errorMsgMap=new HashMap<>(); //實現通用的通過格式化字符串信息獲取錯誤結果的msg方法 public String getErrMsg(){ return StringUtils.join(errorMsgMap.values().toArray(),","); } public boolean isHasError() { return hasError; } public void setHasError(boolean hasError) { this.hasError = hasError; } public Map<String, String> getErrorMsgMap() { return errorMsgMap; } public void setErrorMsgMap(Map<String, String> errorMsgMap) { this.errorMsgMap = errorMsgMap; } }
5.controller方法入參加入校驗
@Autowired private ValidatorImpl validator; @Override @Transactional(rollbackFor = Exception.class) public void register(UserModel userModel) throws BusinessException { UserDo userDo=new UserDo(); if (userModel == null) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //validate進行入參校驗 ValidationResult validate = validator.validate(userModel); if (validate.isHasError()){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,validate.getErrMsg()); } }
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 附加的約束
Hibernate Validator 附加的 constraint:
@Email
被註釋的元素必須是電子郵箱地址@Length
被註釋的字符串的大小必須在指定的范圍內@NotEmpty
被註釋的字符串的必須非空@Range
被註釋的元素必須在合適的范圍內
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Spring 使用Validation 驗證框架的問題詳解
- SpringBoot 中使用 Validation 校驗參數的方法詳解
- @Valid 校驗無效,BindingResult未獲得錯誤的解決
- springmvc項目使用@Valid+BindingResult遇到的問題
- SpringBoot參數校驗的方法總結