如何使用Bean Validation 解決業(yè)務(wù)中參數(shù)校驗(yàn)
前言
在開(kāi)發(fā)中經(jīng)常需要寫一些字段校驗(yàn)的代碼,比如字段非空,字段長(zhǎng)度限制,郵箱格式驗(yàn)證等等,寫這些與業(yè)務(wù)邏輯關(guān)系不大的代碼個(gè)人感覺(jué)有點(diǎn)麻煩:
驗(yàn)證代碼繁瑣,重復(fù)勞動(dòng)
方法內(nèi)代碼顯得冗長(zhǎng)
每次要看哪些參數(shù)驗(yàn)證是否完整,需要去翻閱驗(yàn)證邏輯代碼
敘述
Bean Validation是一個(gè)通過(guò)配置注解來(lái)驗(yàn)證參數(shù)的框架,它包含兩部分Bean Validation API和Hibernate Validator。
Bean Validation API是Java定義的一個(gè)驗(yàn)證參數(shù)的規(guī)范。
Hibernate Validator是Bean Validation API的一個(gè)實(shí)現(xiàn)。
@Valid和Validated的比較
Spring Validation驗(yàn)證框架對(duì)參數(shù)的驗(yàn)證機(jī)制提供了@Validated(Spring's JSR-303規(guī)范,是標(biāo)準(zhǔn)JSR-303的一個(gè)變種),javax提供了@Valid(標(biāo)準(zhǔn)JSR-303規(guī)范),配合BindingResult可以直接提供參數(shù)驗(yàn)證結(jié)果。
@Valid : 沒(méi)有分組功能,可以用在方法、構(gòu)造函數(shù)、方法參數(shù)和成員屬性(field)上,如果一個(gè)待驗(yàn)證的pojo類,其中還包含了待驗(yàn)證的對(duì)象,需要在待驗(yàn)證對(duì)象上注解@valid,才能驗(yàn)證待驗(yàn)證對(duì)象中的成員屬性
@Validated :提供分組功能,可以在入?yún)Ⅱ?yàn)證時(shí),根據(jù)不同的分組采用不同的驗(yàn)證機(jī)制,用在類型、方法和方法參數(shù)上。但不能用于成員屬性(field)。
兩者都可以用在方法入?yún)⑸?,但都無(wú)法單獨(dú)提供嵌套驗(yàn)證功能,都能配合嵌套驗(yàn)證注解@Valid進(jìn)行嵌套驗(yàn)證。
嵌套驗(yàn)證示例:
public class ClassRoom{ @NotNull String name; @Valid // 嵌套校驗(yàn),校驗(yàn)參數(shù)內(nèi)部的屬性 @NotNull Student student; }
@GetMapping("/room") // 此處可使用 @Valid 或 @Validated, 將會(huì)進(jìn)行嵌套校驗(yàn) public String validator(@Validated ClassRoom classRoom, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
BindingResult 的使用
BindingResult必須跟在被校驗(yàn)參數(shù)之后,若被校驗(yàn)參數(shù)之后沒(méi)有BindingResult對(duì)象,將會(huì)拋出BindException。
@GetMapping("/room") public String validator(@Validated ClassRoom classRoom, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
不要使用 BindingResult 接收String等簡(jiǎn)單對(duì)象的錯(cuò)誤信息。簡(jiǎn)單對(duì)象校驗(yàn)失敗,會(huì)拋出 ConstraintViolationException。主要就是接不著,你要寫也算是沒(méi)關(guān)系…
// ❌ 錯(cuò)誤用法,也沒(méi)有特別的錯(cuò),只是 result 是接不到值。 @GetMapping("/room") @Validated // 啟用校驗(yàn) public String validator(@NotNull String name, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
修改校驗(yàn)失敗的提示信息
可以通過(guò)各個(gè)校驗(yàn)注解的message屬性設(shè)置更友好的提示信息。
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屬性配置國(guó)際化的消息也可以的,message中填寫國(guó)際化消息的code,在拋出異常時(shí)根據(jù)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入?yún)?duì)象屬性加入注解
@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 = "手機(jī)號(hào)不能不填") private String telphone; private String registerMode; private String thirdPartyId; private String encrptPassward; }
方法一:3.controller方法入?yún)⒓尤胄r?yàn)(@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; //實(shí)現(xiàn)校驗(yàn)方法并返回校驗(yàn)結(jié)果 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<>(); //實(shí)現(xiàn)通用的通過(guò)格式化字符串信息獲取錯(cuò)誤結(jié)果的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方法入?yún)⒓尤胄r?yàn)
@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進(jìn)行入?yún)⑿r?yàn) 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)
被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值@Max(value)
被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值@DecimalMin(value)
被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值@DecimalMax(value)
被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值@Size(max, min)
被注釋的元素的大小必須在指定的范圍內(nèi)@Digits (integer, fraction)
被注釋的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi)@Past
被注釋的元素必須是一個(gè)過(guò)去的日期@Future
被注釋的元素必須是一個(gè)將來(lái)的日期@Pattern(value)
被注釋的元素必須符合指定的正則表達(dá)式
Hibernate Validator 附加的約束
Hibernate Validator 附加的 constraint:
@Email
被注釋的元素必須是電子郵箱地址@Length
被注釋的字符串的大小必須在指定的范圍內(nèi)@NotEmpty
被注釋的字符串的必須非空@Range
被注釋的元素必須在合適的范圍內(nèi)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Redis的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot整合Redis的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Java源碼解析ArrayList及ConcurrentModificationException
今天小編就為大家分享一篇關(guān)于Java源碼解析ArrayList及ConcurrentModificationException,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01Mybatis結(jié)果集映射一對(duì)多簡(jiǎn)單入門教程
本文給大家介紹Mybatis結(jié)果集映射一對(duì)多簡(jiǎn)單入門教程,包括搭建數(shù)據(jù)庫(kù)環(huán)境的過(guò)程,idea搭建maven項(xiàng)目的代碼詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-06-06mybatis+springboot中使用mysql的實(shí)例
在軟件開(kāi)發(fā)中,數(shù)據(jù)庫(kù)的引入是必不可少的,這里來(lái)展現(xiàn)一下通過(guò)mybatis框架在springboot中使用mysql,具有一定的參考價(jià)值,感興趣的可以了解一下2021-07-07Java對(duì)象的復(fù)制三種方式(小結(jié))
這篇文章主要介紹了Java對(duì)象的復(fù)制三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Spring AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)功能的示例代碼
本篇文章主要介紹了Spring AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)功能的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12MyBatis在注解上使用動(dòng)態(tài)SQL方式(@select使用if)
這篇文章主要介紹了MyBatis在注解上使用動(dòng)態(tài)SQL方式(@select使用if),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Java實(shí)現(xiàn)學(xué)生選課管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)學(xué)生選課管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07