javascript
JSR303校验的简单使用以及自定义校验规则的代码编写
文章目錄
- 一、JSR303校驗
- 1、簡介
- 2、相關注解
- 3、JSR303依賴包
- 二、JSR303自帶的校驗規則
- 1、在JavaBean上添加校驗規則
- 2、生效校驗規則
- 2.1 controller返回的通用實體類R
- 2.2 方法一:在controller的方法中生效校驗
- 2.3 方法二:寫一個異常處理類
- 2、使用group校驗
- 2.1 分組校驗
- 2.1 @Validated與@Valid
- 2.2 定義接口,充當標識
- 2.3 測試
- 3、其他的校驗規則
- 3.1 組序列
- 3.2 級聯驗證
- 三、自定義校驗規則
- 1、編寫自定義注解
- 2、編寫配置文件ValidationMessages.properties
- 3、測試
環境:
IDEA: 2021
JDK: 1.8
Spring-Boot-dependencies: 2.2.5.RELEASE
mysql: 5.7
mybatis-plus-boot-starter: 3.2.0
一、JSR303校驗
1、簡介
參數校驗是程序開發中必不可少的步驟。用戶在前端頁面上填寫表單時,前端js程序會校驗參數的合法性,當數據到了后端,為了防止惡意操作,保持程序的健壯性,后端同樣需要對數據進行校驗。后端參數校驗最簡單的做法是直接在業務方法里面進行判斷,當判斷成功之后再繼續往下執行。但這樣帶給我們的是代碼的耦合,冗余。當我們多個地方需要校驗時,我們就需要在每一個地方調用校驗程序,導致代碼很冗余,且不美觀。
那么如何優雅的對參數進行校驗呢?JSR303就是為了解決這個問題出現的。
2、相關注解
JSR303 是一套JavaBean參數校驗的標準,它定義了很多常用的校驗注解,我們可以直接將這些注解加在我們JavaBean的屬性上面,就可以在需要校驗的時候進行校驗了。
注解如下:
| @NotNull | 注解元素必須是非空 |
| @NotBlank | 注解元素不能是空格并且至少包含一個字符 |
| @NotEmpty | 注解元素不能為null或空 |
| 該字符串必須是格式正確的電子郵件地址。 | |
| @Null | 注解元素必須是空 |
| @Digits | 帶注釋的元素必須是可接受范圍內的數字 |
| @Future | 帶注釋的元素必須是未來的瞬間、日期或時間。 |
| @FutureOrPresent | 注釋元素必須是當前或未來的瞬間、日期或時間。 |
| @Past | 帶注釋的元素必須是過去的瞬間、日期或時間。 |
| @PastOrPresent | 帶注釋的元素必須是過去或現在的瞬間、日期或時間。 |
| @Max | 帶注釋的元素必須是一個數字,其值必須小于或等于指定的最大值。 |
| @Min | 帶注釋的元素必須是一個數字,其值必須大于或等于指定的最小值。 |
| @Pattern | 帶注釋的 {@code CharSequence} 必須匹配指定的正則表達式。正則表達式遵循 Java 正則表達式約定 |
| @Size | 驗證元素大小是否在指定范圍內 |
| @DecimalMax | 帶注釋的元素必須是一個數字,其值必須小于或等于指定的最大值。 |
| @DecimalMin | 帶注釋的元素必須是一個數字,其值必須大于或等于指定的最小值。 |
| @AssertTrue | 被注釋的元素必須為true |
| @AssertFalse | 被注釋的元素必須為false |
| @Positive | 被注解的元素必須是整數 |
| @PositiveOrZero | 被注解元素必須是正數或0 |
Hibernate validator 在JSR303的基礎上對校驗注解進行了擴展,hibernate-validator官方文檔
擴展注解如下:
例舉幾個:
| @URL | 被注釋的元素必須是合法的URL |
| @Length | 被注釋的字符串的大小必須在指定的范圍內 |
| @Range | 被注釋的元素必須在合適的范圍內 |
3、JSR303依賴包
<!--JSR303依賴包--><dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version></dependency><!--hibernate-validator依賴包,包含了JSR303的依賴包--><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.17.Final</version></dependency>二、JSR303自帶的校驗規則
1、在JavaBean上添加校驗規則
package com.zhuang.mall.product.entity;import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable; import java.util.Date; import lombok.Data; import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* * @author mrzhuang* @email 862627527@qq.com* @date 2022-04-11 13:56:19*/ @Data @TableName("pms_brand") public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必須填寫!")private String name;/*** 品牌logo地址*/@NotEmpty(message = "logo url必須填寫!")@URL(message = "必須是合法的url!")private String logo;/*** 介紹*/private String descript;/*** 顯示狀態[0-不顯示;1-顯示]*/private Integer showStatus;/*** 檢索首字母*/@NotEmpty(message = "首字母必須填寫!")// 正則表達式不能寫為"/^[a-zA-Z]$/"@Pattern(regexp = "^[a-zA-Z]$" , message = "檢索首字母必須是一個字母!")private String firstLetter;/*** 排序*/@NotNull(message = "排序必須填寫!")@Min(value = 0, message = "排序必須是大于等于0的整數!")private Integer sort;}}2、生效校驗規則
2.1 controller返回的通用實體類R
/*** Copyright (c) 2016-2019 人人開源 All rights reserved.** https://www.renren.io** 版權所有,侵權必究!*/package com.zhuang.common.utils;import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.http.HttpStatus;import java.util.HashMap; import java.util.Map;/*** @author mrzhuang* @date 2022/4/25 8:12 PM*/ public class R extends HashMap<String, Object> {private static final long serialVersionUID = 1L;public R() {put("code", HttpStatus.SC_OK);put("msg", "success");}public R setData(Object data){put("data",data);return this;}public <T> T getData(String key,TypeReference<T> typeReference){Object data = get(key);String s = JSON.toJSONString(data);T t = JSON.parseObject(s, typeReference);return t;}public <T> T getData(TypeReference<T> typeReference){Object data = get("data");String s = JSON.toJSONString(data);T t = JSON.parseObject(s, typeReference);return t;}public static R error() {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知異常,請聯系管理員");}public static R error(String msg) {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);}public static R error(int code, String msg) {R r = new R();r.put("code", code);r.put("msg", msg);return r;}public static R ok(String msg) {R r = new R();r.put("msg", msg);return r;}public static R ok(Map<String, Object> map) {R r = new R();r.putAll(map);return r;}public static R ok() {return new R();}public R put(String key, Object value) {super.put(key, value);return this;}public Integer getCode(){return (Integer) this.get("code");} }2.2 方法一:在controller的方法中生效校驗
package com.zhuang.mall.product.controller;import java.util.Arrays; import java.util.HashMap; import java.util.Map;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;import com.zhuang.mall.product.entity.BrandEntity; import com.zhuang.mall.product.service.BrandService; import com.zhuang.common.utils.PageUtils; import com.zhuang.common.utils.R;import javax.naming.Binding; import javax.validation.Valid;/*** 品牌* @author mrzhuang* @email 862627527@qq.com* @date 2022-04-11 13:56:19*/ @RestController @RequestMapping("product/brand") public class BrandController {@Autowiredprivate BrandService brandService;/*** 保存*///@Valid告訴spring mvc上傳的數據需要校驗,brandEntity中的@NotBlank規則就會生效!//BindingResult會獲取到錯誤的信息結果@RequestMapping("/save")public R save(@RequestBody @Valid BrandEntity brand, BindingResult result){//判斷result中是否有錯誤if (result.hasErrors()) {Map<String, String > map = new HashMap<>();//獲取到校驗的錯誤結果result.getFieldErrors().forEach((item)->{//FiledError 獲取到錯誤提示String message = item.getDefaultMessage();//獲取錯誤的屬性的名字String field = item.getField();map.put(field, message);});return R.error(400, "提交的數據不合法").put("data",map);}else {brandService.save(brand);return R.ok();}}測試:
使用Postman進行接口的測試。
錯誤的輸入測試:
1、傳入空的jason字符串:
2、傳入不合校驗規則的Jason字符串:
{"name":"","logo":"123","sort":-1,"firstLetter":"ab"}正確的輸入測試:
傳入的jason字符串為:
2.3 方法二:寫一個異常處理類
對于第一種方法,代碼的冗余度比較高。使用統一的異常處理類降低冗余、方便。
1、創建通用的異常枚舉類
mall-common/src/main/java/com/zhuang/common/exception/BizCodeEnum.java
2、創建異常類
mall-product/src/main/java/com/zhuang/mall/product/exception/MallExceptionControllerAdvice.java
3、還原初始controller類中的方法
方法中的@Valid注解需要加上!!!
4、測試
輸入錯誤的jason字符串:
輸入正確的jason字符串:
{"name":"小米","logo":"https://gss0.baidu.com/7LsWdDW5_xN3otqbppnN2DJv/forum/pic/item/fd039245d688d43f394f6821381ed21b0ff43b7b.jpg","sort":0,"showStatus":"0","firstLetter":"x"}2、使用group校驗
2.1 分組校驗
分組校驗可以將JavaBean的某個屬性值進行分組,比如在進行更新操作或者添加操作的時候需要進行校驗;
對于brangId我們需要在添加操作的時候,可以不需要branId,因為采用的是自增長策略,因此不需要有brandId,這時需要校驗必須為空值。而對于更新操作的時候,我們需要brandId,這時需要校驗是否是空值。
2.1 @Validated與@Valid
@Validated與@Valid的比較
@Validated:
Spring提供的
支持分組校驗
可以用在類型、方法和方法參數上。但是不能用在成員屬性(字段)上
由于無法加在成員屬性(字段)上,所以無法單獨完成級聯校驗,需要配合@Valid
@Valid:
JDK提供的(標準JSR-303規范)
不支持分組校驗
可以用在方法、構造函數、方法參數和成員屬性(字段)上
可以加在成員屬性(字段)上,能夠獨自完成級聯校驗
2.2 定義接口,充當標識
1、添加AddGroup與UpdateGroup接口
package com.zhuang.common.valid; /*** @author mrzhuang* @date 2022/4/25 9:18 PM */ public interface AddGroup { } package com.zhuang.common.valid; /*** @author mrzhuang* @date 2022/4/25 9:18 PM */ public interface UpdateGroup { }2、給屬性上添加分組
/*** 品牌id*/@NotNull(message = "更新操作必須填寫brandId!", groups = {UpdateGroup.class})@Null(message = "添加操作必須為空值!", groups = {AddGroup.class})@TableIdprivate Long brandId;3、controller類中的方法
添加操作
更新操作
@RequestMapping("/update")public R update(@RequestBody @Validated({UpdateGroup.class}) BrandEntity brand){brandService.updateById(brand);return R.ok();}2.3 測試
添加操作:
輸入的jason字符串:
結果:
{"msg": "參數校驗異常","code": 10001,"data": {"brandId": "添加操作必須為空值!"} }更新操作:
輸入的jason字符串:
注意:
從上面結果中看出@Validated注解無法校驗屬性上定義的規則!,而且當方法中同時使用@Validated與@Valid時,只會生效左邊的第一個,也就是誰靠左就生效誰!!!
原因是:
沒有加上默認分組 Default.class。
解決方法:
1、@Validated({UpdateGroup.class)中的分組中加上默認分組
@Validated({AddGroup.class,Default.class})
@Validated({UpdateGroup.class, Default.class})
2、在寫的充當標識的接口時繼承默認分組
public interface AddGroup extends Default{
}
public interface UpdateGroup extends Default{
}
修改后的方法:
添加操作
更新操作
@RequestMapping("/update")public R update(@RequestBody @Validated({UpdateGroup.class, Default.class}) BrandEntity brand){brandService.updateById(brand);return R.ok();}3、其他的校驗規則
3.1 組序列
指定組與組之間的檢驗順序,如果第一個組校驗沒過,就不會校驗后面的組
@GroupSequence({UpdateGroup.class, AddGroup.class, Default.class}) public interface DefaultGroupSequence { }注意: 需要重新自定義一個接口。
3.2 級聯驗證
一個待驗證的JavaBean類,其中又包含了一個待驗證的對象。
需要在待驗證的對象屬性上加@Valid注解
三、自定義校驗規則
/*** 顯示狀態[0-不顯示;1-顯示]*/private Integer showStatus;我們需要在showStatus屬性上添加一個自定義校驗規則@ListValue:輸入的值只能為0或1
1、編寫自定義注解
創建自定義注解ListValue,可以參考JSR303已有的注解的內容。
mall-common/src/main/java/com/zhuang/common/valid/ListValue.java
我們還需要指定校驗方式ListValueConstraintValidator:校驗我們輸入的值是否符合要求
@Constraint(validatedBy = {ListValueConstraintValidator.class} )創建ListValueConstraintValidator類,mall-common/src/main/java/com/zhuang/common/valid/ListValueConstraintValidator.java
查看validatedBy源碼,可以看出validatedBy中的元素是ConstraintValidator<?, ?>的子類。
@Documented @Target({ ANNOTATION_TYPE }) @Retention(RUNTIME) public @interface Constraint {/*** {@link ConstraintValidator} classes implementing the constraint. The given classes* must reference distinct target types for a given {@link ValidationTarget}. If two* {@code ConstraintValidator}s refer to the same type, an exception will occur.* <p>* At most one {@code ConstraintValidator} targeting the array of parameters of* methods or constructors (aka cross-parameter) is accepted. If two or more* are present, an exception will occur.** @return array of {@code ConstraintValidator} classes implementing the constraint*/Class<? extends ConstraintValidator<?, ?>>[] validatedBy(); }因此,我們寫ListValueConstraintValidator類時,需要實現ConstraintValidator<?, ?>。
查看ConstraintValidator源碼:
public interface ConstraintValidator<A extends Annotation, T> {/*** Initializes the validator in preparation for* {@link #isValid(Object, ConstraintValidatorContext)} calls.* The constraint annotation for a given constraint declaration* is passed.* <p>* This method is guaranteed to be called before any use of this instance for* validation.* <p>* The default implementation is a no-op.** @param constraintAnnotation annotation instance for a given constraint declaration*/default void initialize(A constraintAnnotation) {}/*** Implements the validation logic.* The state of {@code value} must not be altered.* <p>* This method can be accessed concurrently, thread-safety must be ensured* by the implementation.** @param value object to validate* @param context context in which the constraint is evaluated** @return {@code false} if {@code value} does not pass the constraint*/boolean isValid(T value, ConstraintValidatorContext context); }從上可以看出,ConstraintValidator中的泛型A為ListValue注解,T為獲得的數據值value。
因此ListValueConstraintValidator類的內容為:
package com.zhuang.common.valid;import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set;/*** @author mrzhuang* @date 2022/4/26 10:23 AM*/ public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {Set<Integer> set = new HashSet<Integer>();/*** 初始化* @param constraintAnnotation*/@Overridepublic void initialize(ListValue constraintAnnotation) {int[] vals = constraintAnnotation.vals();for (int val : vals) {set.add(val);}}/*** 真正的校驗規則* 判斷是否校驗成功* @param integer* @param constraintValidatorContext* @return*/@Overridepublic boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {//判斷輸入的值是否是規定的值return set.contains(integer);} }2、編寫配置文件ValidationMessages.properties
編寫配置文件,輸出message信息
mall-common/src/main/resources/ValidationMessages.properties
內容為:
3、測試
1、加上自定義的@ListValue注解
/*** 顯示狀態[0-不顯示;1-顯示]*/@ListValue(vals = {0, 1})private Integer showStatus;2、輸入的jason字符串:
{"brandId":"","name":"小米","logo":"https://gss0.baidu.com/7LsWdDW5_xN3otqbppnN2DJv/forum/pic/item/fd039245d688d43f394f6821381ed21b0ff43b7b.jpg","sort":1,"showStatus":"2","firstLetter":"x"}showStatus為2時,不是指定的值!校驗錯誤!
結果:
當設置showStatus為0或者1時,校驗成功!
{"msg": "success","code": 200 }總結
以上是生活随笔為你收集整理的JSR303校验的简单使用以及自定义校验规则的代码编写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: es查询简单场景问题小记
- 下一篇: Adobe XD 下载和安装教程