#頭條創作挑戰賽#
一、JSR 303
1、什麼是 JSR 303?
- • JSR 是 Java Specification Requests 的縮寫,即 Java 規範提案。
- • 存在各種各樣的 JSR,簡單的了解為 JSR 是一種 Java 标準。
- • JSR 303 就是資料檢驗的一個标準(Bean Validation (JSR 303))。
參考:https://www.jianshu.com/p/554533f88370
2、為什麼使用 JSR 303?
- • 處理一段業務邏輯,首先要確定資料輸入的正确性,是以需要先對資料進行檢查,保證資料在語義上的正确性,再根據資料進行下一步的處理。
- • 前端可以通過 js 程式校驗資料是否合法,後端同樣也需要進行校驗。而後端最簡單的實作就是直接在業務方法中對資料進行處理,但是不同的業務方法可能會出現同樣的校驗操作,這樣就出現了資料的備援。為了解決這個情況,JSR 303 出現了。
- • JSR 303 使用 Bean Validation,即在 Bean 上添加相應的注解,去實作資料校驗。這樣在執行業務方法前,都會根據注解對資料進行校驗,進而減少自定義的校驗邏輯,減少代碼備援。
3、JSR 303 常見操作?
(1)可以通過簡單的注解校驗 Bean 屬性,比如 @NotNull、@Null 等。 (2)可以通過 Group 分組自定義需要執行校驗的屬性。 (3)可以自定義注解并指定校驗規則。 (4)支援基于 JSR 303 的實作,比如 Hibernate Validator(額外添加一些注解)。
二、使用 JSR 303 相關注解處理邏輯
1、JSR 303 注解處理邏輯
1.1 使用步驟
- • 1、在相關的 Bean 上标注需要處理的注解,并指定需要提示的資訊(若不指定,會從預設配置檔案中讀取預設的資訊)。
- • 2、在相關的方法上,使用 @Valid 注解(或者 @Validated 指定組名)标記需要被校驗的資料,否則會不生效。
注意:檢測到資料異常後,系統會向外抛出異常,如果做了統一異常處理,可以根據 postman 測試的結果,找到控制台列印出的相應的異常,并處理。
- • 3、處理異常。使用 BindingResult 可以擷取到檢測結果,然後進行處理。也可以使用 全局統一異常 處理(@RestControllerAdvice 與 @ExceptionHandler),處理檢測結果
1.2 實際應用
1、給Bean添加校驗注解:javax.validation.constraints,并定義自己的message提示
import javax.validation.constraints.*;
/**
* 品牌
*
* @author zhengyuzhu
* @email [email protected]
* @date 2023-02-25 23:11:31
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必須指定品牌id")
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必須送出")
private String name;
}
2、修改 Controller 方法,使用 @Valid 注解标記需要檢測的資料、比如在常見的增加和修改方法上,這個時候需要将前台傳來的資料插入資料庫
@RestController
@RequestMapping("product/brand")
public class BrandController {
@Autowired
private BrandService brandService;
/**
* 儲存新添加品牌
*/
@RequestMapping("/save")
public R save(@Validated @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
return R.ok();
}
}
提示:這個時候如果插入的資料不符合規範,就會抛出異常、為了避免代碼備援。可以使用全局異常來進行處理
3、集中處理異常
可以使用 BindingResult 去處理捕獲到的資料并進行相關處理
/**
* 集中處理所有異常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.zyz.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
log.error("資料校驗出現問題{},異常類型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
// 擷取校驗結果,周遊擷取捕獲到的每個校驗結果
bindingResult.getFieldErrors().forEach((fieldError)->{
// 存儲得到的校驗結果
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("錯誤:",throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
2 、JSR 303 分組校驗
2.1 為什麼使用 分組校驗?
如果出現多個方法,都需要校驗 Bean,且校驗規則不同的時候,怎麼辦呢?
就比如下方,修改品牌ID的時候,送出為空的時候,提示使用者的應該是修改ID不能為空。新增的時候,應該是提示使用者,新增ID不能為空。但是你也可以直接寫ID不能為空,但總感覺缺少點啥。
分組校驗就可以去解決該問題,每個分組指定不同的校驗規則,不同的方法執行不同的分組,就可以得到不同的校驗結果。 也就是說,定義好分組後,在校驗的時候,就可以去找對應的分組進行相關的資訊提示。
/**
* 品牌id
*/
@NotNull(message = "修改必須指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
2.2 使用步驟
- • 1、定義一個空接口,用于指定分組,内部不需要任何實作。
- • 2、指定 注解時,通過 groups 指定分組。用于指定在某個分組條件下,才去執行校驗規則。
- • 3、在相關的業務方法上,通過 @Validated 注解指定分組,去指定校驗。
注:使用分組校驗後,Bean 注解上若不指定分組,則不會執行校驗規則。
2.3 實際應用
1 建立兩個新增和修改接口
public interface AddGroup {
}
public interface UpdateGroup {
}
2 指定 注解時,通過 groups 指定分組
比如,這裡的品牌id定義兩個分組,一個是增加時候的校驗,一個是修改時候的校驗,對應不同的校驗規則。也可以将通用的校驗規則,以分組的時候同時管理多個。
/**
* 品牌id
*/
@NotNull(message = "修改必須指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必須送出",groups = {AddGroup.class,UpdateGroup.class})
private String name;
3、在相關的業務方法上,通過 @Validated 注解指定分組,去指定校驗。
/**
* 儲存新添加品牌
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
return R.ok();
}
在這裡插入圖檔描述
3、JSR 303 自定義校驗注解
3.1 為什麼使用自定義校驗注解?
當上面的注解滿足不了業務需求時,可以自定義校驗注解,自定義校驗規則。
3.2 使用步驟
- • 1、需要自定義一個校驗注解。可以建立一個 ValidationMessages.properties 用于儲存預設的 message 資訊。
- • 2、需要自定義一個校驗器,即自定義校驗規則。實作 ConstraintValidator 接口,并重寫相關方法。
注:initialize 方法用于初始化,可以擷取 自定義的屬性的值。isValid 方法用于校驗,可以擷取到實際的值,然後與自定義的屬性值進行比較。
- • 3、将校驗注解 與 校驗器 關聯起來。@Constraint(validatedBy = {TestValidConstraintValidator.class})
3.3 實際應用
如下例,自定義一個校驗規則,判斷資料是否是 0,1。當傳來的資料不是這兩種之一,校驗不通過。
- • 1、自定義一個校驗注解
/**
* @author zyz
*/
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
/**
* 添加校驗
* @return
*/
String message() default "{com.zyz.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
建立一個檔案ValidationMessages.properties。當不符合自定義規則校驗的時候,message可以擷取到對應的資訊
配置檔案内容:com.zyz.common.valid.ListValue.message=必須送出指定的值啊
在這裡插
入圖檔描述
- • 2 自定義一個校驗器TestValidConstraintValidator, 用于檢測值是否合法。
/**
* @author zyz
*/
/**
* 實作 ConstraintValidator 接口,
* 其中 ConstraintValidator 的泛型,一個需要指定自定義的注解,一個需要指定需要擷取的值的類型。
* 比如:
* ConstraintValidator<ListValue , String> 中
* ListValue 表示自定義注解
* String 表示擷取的值的類型
* 即定義規則,判斷一個 String 的值的長度是否滿足條件
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判斷是否校驗成功
/**
*
* @param value 需要校驗的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
- • 3、使用注解
使用自定義注解,這裡的@ListValue 就是使用的自定義注解,目前端傳來狀态的值不是對應資料。就會走自定義校驗判斷、提示對應的資訊。這裡也使用了分組的形式。
/**
* 顯示狀态[0-不顯示;1-顯示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
方法中使用
這裡使用了分組,就會自動校驗
/**
* 儲存新添加品牌
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
資料參考:JSR 303[1]
引用連結
[1] JSR 303: https://www.cnblogs.com/l-y-h/p/12797809.html#_label0_0