<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在日常的專案開發中,為了防止非法引數對業務造成的影響,需要對介面的引數做合法性校驗,例如在建立使用者時,需要效驗使用者的賬號名稱不能輸入中文與特殊字元,手機號、郵箱格式是否準確。按照原始的處理邏輯需要對每個介面中的引數進行 if/else 處理,如果這樣開發,後期程式碼難以維護,可讀性極差。
為了解決上述問題,validation框架誕生了,程式碼量大大減少,引數的效驗不再穿插業務邏輯程式碼中,程式碼美觀又易於維護。
@Valid 是 JSR303 宣告的,JSR是Java Specification Requests的縮寫,其中 JSR303 是JAVA EE 6 中的一項子規範,叫做 Bean Validation,為 JavaBean 驗證定義了相應的後設資料模型和 API,需要注意的是,JSR 只是一項標準,它規定了一些校驗註解的規範,但沒有實現,而 Hibernate validation 對其進行實現。
Spring Validation 驗證框架對引數的驗證機制提供了@Validated(Spring JSR-303規範,是標準JSR-303的一個變種)。
區別 | @Valid | @Validated |
---|---|---|
來源 | JSR-303規範 | Spring |
是否支援分組 | 不支援 | 支援 |
標註位置 | METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE | TYPE,METHOD,PARAMETER |
巢狀校驗 | 支援 | 不支援 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.12.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency>
注:從 boot-2.3.x開始,spring-boot-starter-web不再引入 spring-boot-starter-validation,所以需要額外手動引入validation依賴,而 2.3之前的版本只需要引入 web 依賴。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.3.12.RELEASE</version> </dependency> <!-- <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.18.Final</version> <scope>compile</scope> </dependency>-->
以上兩個依賴都是可以實現功能的。hibernate-validator、spring-boot-starter-validation底層都引入了 jakarta.validation-api依賴。
在實際開發的過程中,請求引數的格式一般有如下幾種情況:
使用物件引數接收分為兩種,一種是使用 @RequestBody註解的application/json提交,還有一種不使用 @RequestBody註解的 form-data提交。
@Data public class UserReq { @NotBlank(message = "name為必傳引數") private String name; @NotBlank(message = "email為必傳引數") private String email; }
@RestController public class GetHeaderController { @PostMapping("save") public void save(@RequestBody @Validated UserReq req){} }
只需要校驗的物件前面加@Validated註解或者@Valid註解,如果校驗失敗,會丟擲BindException異常。
@PostMapping("save2") public void save2(@Validated UserReq req){ }
@RestController @Validated public class GetHeaderController { @PostMapping("get") public void get(@NotBlank(message = "名稱 is required") String name,@NotBlank(message = "郵箱 is required") String email) throws JsonProcessingException { } }
通過前面的測試,我們知道如果引數校驗失敗,三種使用場景會丟擲三種異常或者警告,分別是MethodArgumentNotValidException、ConstraintViolationException、BindException異常,每種異常的響應格式又不一致。所以在專案開發中,通常會使用統一例外處理來返回一個統一格式並友好的提示。
@RestControllerAdvice public class GlobalExceptionHandler { /** * @RequestBody 上校驗失敗後丟擲的異常是 MethodArgumentNotValidException 異常。 */ @ExceptionHandler(MethodArgumentNotValidException.class) public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { BindingResult bindingResult = e.getBindingResult(); String messages = bindingResult.getAllErrors() .stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.joining(";")); return messages; } /** * 不加 @RequestBody註解,校驗失敗丟擲的則是 BindException */ @ExceptionHandler(value = BindException.class) public String exceptionHandler(BindException e){ String messages = e.getBindingResult().getAllErrors() .stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.joining(";")); return messages; } /** * @RequestParam 上校驗失敗後丟擲的異常是 ConstraintViolationException */ @ExceptionHandler({ConstraintViolationException.class}) public String methodArgumentNotValid(ConstraintViolationException exception) { String message = exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";")); return message; } }
可以發現它是將類中所有的屬性進行效驗完成之後,才丟擲異常的,但其實這有點消耗效能,那能不能只要檢測到一個效驗不通過的,就丟擲異常呢?只需要在容器提供如下程式碼:
@Configuration public class ParamValidatorConfig { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() //failFast:只要出現校驗失敗的情況,就立即結束校驗,不再進行後續的校驗。 .failFast(true) .buildValidatorFactory(); return validatorFactory.getValidator(); } @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor(); methodValidationPostProcessor.setValidator(validator()); return methodValidationPostProcessor; } }
MethodValidationPostProcessor是Spring提供的來實現基於方法Method的JSR校驗的核心處理器,最終會由 MethodValidationInterceptor進行校驗攔截。
上面舉例使用了NotBlank註解,但肯定不只一個!我們進入到 @NotBlank註解所在的包路徑。
哦豁,這麼多呀!小杰一個一個來介紹一下作用。
註解 | 備註 | 適用型別 | 範例 |
---|---|---|---|
@AssertFalse | 被註釋的元素必須為 false,null 值是有效的。 | boolean 和 Boolean | @AssertFalse(message = "該引數必須為 false") |
@AssertTrue | 被註釋的元素必須為 true,null 值是有效的。 | boolean 和 Boolean | @AssertTrue(message = "該引數必須為 true") |
@DecimalMax | 被註釋的元素必須是一個數位,其值必須小於或等於指定的最大值,null 值是有效的。 | BigDecimal、BigInteger、CharSequence、byte、short、int、long以及包裝型別 | @DecimalMax(value = "100",message = "該引數不能大於 100") |
@DecimalMin | 被註釋的元素必須是一個數位,其值必須大於或等於指定的最小值,null 值是有效的。 | BigDecimal、BigInteger、CharSequence、byte、short、int、long以及包裝型別 | @DecimalMax(value = "0",message = "該引數不能小於 0") |
@Digits | 被註釋的元素必須是可接受範圍內的數位,null 值是有效的。 | BigDecimal、BigInteger、CharSequence、byte、short、int、long以及包裝型別 | @Digits(integer = 3,fraction = 2,message = "該引數整數位數不能超出3位,小數位數不能超過2位") |
@Max | 被註釋的元素必須是一個數位,其值必須小於或等於指定的最大值,null 值是有效 | BigDecimal、BigInteger、byte、short、int、long以及包裝型別 | @Max(value = 200,message = "最大金額不能超過 200") |
@Min | 被註釋的元素必須是一個數位,其值必須大於或等於指定的最小值,null 值是有效的。 | BigDecimal、BigInteger、byte、short、int、long以及包裝型別 | @Min(value = 0,message = "最小金額不能小於 0") |
@Negative | 被註釋的元素必須是負數,null 值是有效 | BigDecimal、BigInteger、byte、short、int、long、float、double 以及包裝型別 | @Negative(message = "必須是負數") |
@NegativeOrZero | 被註釋的元素必須是負數或 0,null 值是有效的。 | BigDecimal、BigInteger、byte、short、int、long、float、double 以及包裝型別 | @NegativeOrZero(message = "必須是負數或者為0") |
@Positive | 被註釋的元素必須是正數,null 值是有效的。 | BigDecimal、BigInteger、byte、short、int、long、float、double 以及包裝型別 | @Positive(message = "必須是正數") |
@PositiveOrZero | 被註釋的元素必須是正數或0,null 值是有效的。 | BigDecimal、BigInteger、byte、short、int、long、float、double 以及包裝型別 | @PositiveOrZero(message = "必須是正數或者為0") |
@Future | 被註釋的元素必須是未來的日期(年月日),null 值是有效的。 | 基本所有的時間型別都支援。常用的:Date、LocalDate、LocalDateTime、LocalTime、Instant | @Future(message = "預約日期要大於當前日期") |
@FutureOrPresent | 被註釋的元素必須是現在或者未來的日期(年月日),null 值是有效的。 | 基本所有的時間型別都支援。常用的:Date、LocalDate、LocalDateTime、LocalTime、Instant | @FutureOrPresent(message = "預約日要大於當前日期") |
@Past | 被註釋的元素必須是過去的日期,null 值是有效的。 | 基本所有的時間型別都支援。常用的:Date、LocalDate、LocalDateTime、LocalTime、Instant | @Past(message = "出生日期要小於當前日期") |
@PastOrPresent | 被註釋的元素必須是過去或者現在的日期,null 值是有效的。 | 基本所有的時間型別都支援。常用的:Date、LocalDate、LocalDateTime、LocalTime、Instant | @PastOrPresent(message = "出生時間要小於當前時間") |
@NotBlank | 被註釋的元素不能為空,並且必須至少包含一個非空白字元 | CharSequence | @NotBlank(message = "name為必傳引數") |
@NotEmpty | 被註釋的元素不能為 null 也不能為空 | CharSequence、Collection、Map、Array | @NotEmpty(message = "不能為null或者為空") |
@NotNull | 被註釋的元素不能為null | 任意型別 | @NotNull(message = "不能為null") |
@Null | 被註釋的元素必須為null | 任意型別 | @Null(message = "必須為null") |
被註釋的元素必須是格式正確的電子郵件地址,null 值是有效的。 | CharSequence | @Email(message = "email格式錯誤,請重新填寫") | |
@Pattern | 被註釋的元素必須匹配指定的正規表示式,null 值是有效的。 | CharSequence | @Pattern(regexp = "^1[3456789]d{9}$",message = "手機號格式不正確") |
@Size | 被註釋的元素大小必須在指定範圍內,null 值是有效的。 | CharSequence、Collection、Map、Array | @Size(min = 5,max = 20,message = "字元長度在 5 -20 之間") |
以上註解有幾個需要注意一下,因為經常用到,也經常使用錯誤
@NotNull:適用於任何型別,不能為null,但可以是 (""," ")
@NotBlank:只能用於 String,不能為null,而且呼叫 trim() 後,長度必須大於0,必須要有實際字元。
@NotEmpty:用於 String、Collection、Map、Array,不能為null,長度必須大於0。
有些小夥伴說使用 @Validated校驗的物件不能複用,這我只能說學的還不夠深入。
小杰使用 PayReq來舉例,該物件是一個公用的請求體,對接了微信、支付寶兩個渠道方,對接微信 payName引數是非必傳的,對接支付寶是必傳引數。payAmount是兩個渠道必傳引數。其實就跟平常寫新增方法、修改方法一樣的,用的是同一個 ReqDTO,但是其中 id 欄位新增是不用傳遞的,而修改時是必傳的。
定義ZfbPayGroup的分組介面,繼承 Default介面。
public interface ZfbPayGroup extends Default { }
在需要區分組的欄位上加 groups 引數。在本例中在 payName加了groups 引數,值為 ZfbPayGroup.class,代表對組為 ZfbPayGroup的進行payName
引數校驗。
@Data public class PayReq { @NotBlank(message = "支付名稱不能為空",groups = {ZfbPayGroup.class}) private String payName; @NotNull(message = "支付金額不能為空") private BigDecimal payAmount; }
注意:ZfbPayGroup 要繼承 Default介面,不然 payAmount欄位的效驗會對ZfbPayGroup這個組失效,payAmount預設的組為 Default。
建立兩個介面,在 zfbPaySave介面中宣告@Validated校驗組,wxbPaySave介面正常編寫。
@PostMapping("zfbPaySave") public void zfbPaySave(@RequestBody @Validated(value = {ZfbPayGroup.class}) PayReq req) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); System.out.println( mapper.writeValueAsString(req)); } @PostMapping("wxbPaySave") public void wxbPaySave(@RequestBody @Validated PayReq req) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); System.out.println( mapper.writeValueAsString(req)); }
什麼是巢狀使用呢?就是一個物件中包含另外一個物件,另外一個物件的欄位也是需要進行校驗。範例如下:
@Data public class UserReq { @NotBlank(message = "name為必傳引數") private String name; private String email; @NotNull(message = "proReq物件不能為空") @Valid private ProReq proReq; }
巢狀校驗需要在效驗的物件加上 @Valid 註解。
@Data public class ProReq { @NotBlank(message = "proName為必傳引數") private String proName; }
在某些場景下,我們需要使用集合接收前端傳遞的引數,並對集合中的每個物件都進行引數校驗。但是這時我們的引數校驗並不會生效!如下寫法:
@PostMapping("save3") public String save3(@RequestBody @Validated List<UserReq> req){ return "成功"; }
下面介紹兩種方式對集合進行效驗!
@Validated + @Valid兩個註解同時使用!缺點:不能使用分組效驗!如果該實體不需要用到分組功能,可以使用該方式!
@RestController @Validated public class GetHeaderController { @PostMapping("save3") public String save3(@RequestBody @Valid @NotEmpty(message = "該集合不能為空") List<UserReq> req){ return "成功"; } }
@Data public class ValidList<E> implements List<E> { // 使用該註解就不需要手動重新 List 中的方法了 @Delegate @Valid public List<E> list = new ArrayList<>(); }
@PostMapping("save4") public String save4(@RequestBody @Validated @NotEmpty(message = "該集合不能為空") ValidList<UserReq> req){ return "成功"; }
自定義校驗規則小杰在工作當中用的比較少。大部分業務需求使用自帶的註解已經夠平常開發了。當然自定義validation規則也非常簡單。這裡使用校驗電話號碼是否合法來舉例!別擡槓,說我為什麼不用 @Pattern(regexp = "^1[3456789]\d{9}$",message = "手機號格式不正確")
直接實現。對不起,我不想每次寫正則。
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented @Constraint(validatedBy = { PhoneValidator.class }) public @interface Phone { String message() default "手機號碼格式異常"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
public class PhoneValidator implements ConstraintValidator<Phone,String> { private static final String REGEX = "^1[3456789]\\d{9}$"; /** * * @param value * @param context * @return:返回 true 表示效驗通過 */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 不為null才進行校驗 if (value != null) { return value.matches(REGEX); } return true; } }
@Data public class UserReq { @NotBlank(message = "name為必傳引數") private String name; @Email(message = "email格式錯誤,請重新填寫") @NotBlank(message = "email為必傳引數") private String email; @NotNull(message = "proReq物件不能為空") @Valid private ProReq proReq; @Phone @NotBlank(message = "手機號碼為必傳引數") private String tel; }
到此這篇關於Spring Validation引數效驗的各種使用姿勢總結的文章就介紹到這了,更多相關Spring Validation引數效驗內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45