一、前言
我們在日常開發中,避不開的就是引數校驗,有人說前端不是會在表單中進行校驗的嗎?在后端中,我們可以直接不管前端怎么樣判斷過濾,我們后端都需要進行再次判斷,為了安全,因為前端很容易拜托,當測驗使用PostMan來測驗,如果后端沒有校驗,不就亂了嗎?肯定會有很多例外的,今天小編和大家一起學習一下JSR303專門用于引數校驗的,算是一個工具吧!
二、JSR303簡介
JSR-303 是 JAVA EE 6 中的一項子規范,叫做 Bean Validation,官方參考實作是Hibernate Validator,
Hibernate Validator 提供了 JSR 303 規范中所有內置 constraint 的實作,除此之外還有一些附加的 constraint,
Hibernate官網
官網介紹:
驗證資料是一項常見任務,它發生在從表示層到持久層的所有應用程式層中,通常在每一層都實作相同的驗證邏輯,這既耗時又容易出錯,為了避免重復這些驗證,開發人員經常將驗證邏輯直接捆綁到域模型中,將域類與驗證代碼混在一起,而驗證代碼實際上是關于類本身的元資料,

Jakarta Bean Validation 2.0 - 為物體和方法驗證定義了元資料模型和 API,默認元資料源是注釋,能夠通過使用 XML 覆寫和擴展元資料,API 不依賴于特定的應用程式層或編程模型,它特別不依賴于 Web 或持久層,并且可用于服務器端應用程式編程以及富客戶端 Swing 應用程式開發人員,

三、匯入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
四、常用注解
| 約束注解名稱 | 約束注解說明 |
|---|---|
| @Null | 用于驗證物件為null |
| @NotNull | 用于物件不能為null,無法查檢長度為0的字串 |
| @NotBlank | 只用于String型別上,不能為null且trim()之后的size>0 |
| @NotEmpty | 用于集合類、String類不能為null,且size>0,但是帶有空格的字串校驗不出來 |
| @Size | 用于物件(Array,Collection,Map,String)長度是否在給定的范圍之內 |
| @Length | 用于String物件的大小必須在指定的范圍內 |
| @Pattern | 用于String物件是否符合正則運算式的規則 |
| 用于String物件是否符合郵箱格式 | |
| @Min | 用于Number和String物件是否大等于指定的值 |
| @Max | 用于Number和String物件是否小等于指定的值 |
| @AssertTrue | 用于Boolean物件是否為true |
| @AssertFalse | 用于Boolean物件是否為false |
所有的大家參考jar包

五、@Validated、@Valid區別
@Validated:
- Spring提供的
- 支持分組校驗
- 可以用在型別、方法和方法引數上,但是不能用在成員屬性(欄位)上
- 由于無法加在成員屬性(欄位)上,所以無法單獨完成級聯校驗,需要配合@Valid
@Valid:
- JDK提供的(標準JSR-303規范)
- 不支持分組校驗
- 可以用在方法、建構式、方法引數和成員屬性(欄位)上
- 可以加在成員屬性(欄位)上,能夠獨自完成級聯校驗
總結:@Validated用到分組時使用,一個學校物件里還有很多個學生物件需要使用@Validated在Controller方法引數前加上,@Valid加在學校中的學生屬性上,不加則無法對學生物件里的屬性進行校驗!
區別參考博客地址
例子:
@Data
public class School{
@NotBlank
private String id;
private String name;
@Valid // 需要加上,否則不會驗證student類中的校驗注解
@NotNull // 且需要觸發該欄位的驗證才會進行嵌套驗證,
private List<Student> list;
}
@Data
public class Student {
@NotBlank
private String id;
private String name;
private int age;
}
@PostMapping("/test")
public Result test(@Validated @RequestBody School school){
}
六、常用使用測驗
1. 物體類添加校驗
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
@Data
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必須有品牌id")
private Long brandId;
/**
* 品牌名F
*/
@NotBlank(message = "品牌名必須提交")
private String name;
/**
* 品牌logo地址
*/
@NotBlank(message = "地址必須不為空")
private String logo;
/**
* 介紹
*/
private String descript;
/**
* 檢索首字母
*/
//正則運算式
@Pattern(regexp = "^[a-zA-Z]$",message = "檢索的首字母必須是字母")
private String firstLetter;
/**
* 排序
*/
@Min(value = https://www.cnblogs.com/wang1221/p/0,message ="排序必須大于等于0")
private Integer sort;
}
2. 統一回傳型別
import com.alibaba.druid.util.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//統一回傳結果
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel
public class Result<T> {
@ApiModelProperty("回應碼")
private Integer code;
@ApiModelProperty("相應資訊")
private String msg;
@ApiModelProperty("回傳物件或者集合")
private T data;
//成功碼
public static final Integer SUCCESS_CODE = 200;
//成功訊息
public static final String SUCCESS_MSG = "SUCCESS";
//失敗
public static final Integer ERROR_CODE = 201;
public static final String ERROR_MSG = "系統例外,請聯系管理員";
//沒有權限的回應碼
public static final Integer NO_AUTH_COOD = 999;
//執行成功
public static <T> Result<T> success(T data){
return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);
}
//執行失敗
public static <T> Result failed(String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(ERROR_CODE,msg,"");
}
//傳入錯誤碼的方法
public static <T> Result failed(int code,String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,"");
}
//傳入錯誤碼的資料
public static <T> Result failed(int code,String msg,T data){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,data);
}
}
3. 測驗類
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity) {
return Result.success("成功");
}
遇到的坑:小編在公司的專案中添加沒什么問題,但是就是無法觸發校驗,看到的是Springboot版本太高了,所有要添加下面的依賴才觸發,
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
</dependency>
4. 普通測驗結果

5. 我們把例外回傳給頁面
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){
if (bindingResult.hasErrors()){
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach(item ->{
map.put(item.getField(),item.getDefaultMessage());
});
return Result.failed(400,"提交的資料不合規范",map);
}
return Result.success("成功");
}
6. 例外處理結果
{
"code": 400,
"data": {
"name": "品牌名必須提交",
"logo": "地址必須不為空"
},
"msg": "提交的資料不合規范"
}
七、抽離全域例外處理
1. 心得體會
上面我們要在每個校驗的介面上面寫,所以我們要抽離出來做個全域例外,并且要改進一下,原來的是把錯誤資訊放到data里,但是正常情況下的data是回傳給前端的資料,我們這樣把例外資料放進去,會使data的資料有二義性,這樣對于前端就不知道里面是資料還是報錯資訊了哈,這樣就可以直接前端展示msg里面的提示即可!
2. 書寫ExceptionControllerAdvice
import com.wang.test.demo.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice(basePackages = "com.wang.test.demo.controller")
public class ExceptionControllerAdvice {
@ExceptionHandler(value = https://www.cnblogs.com/wang1221/p/MethodArgumentNotValidException.class)
public Result handleVaildException(MethodArgumentNotValidException e){
log.error("資料校驗出現問題:{},例外型別:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
StringBuffer stringBuffer = new StringBuffer();
bindingResult.getFieldErrors().forEach(item ->{
//獲取錯誤資訊
String message = item.getDefaultMessage();
//獲取錯誤的屬性名字
String field = item.getField();
stringBuffer.append(field + ":" + message + " ");
});
return Result.failed(400, stringBuffer + "");
}
@ExceptionHandler(value = https://www.cnblogs.com/wang1221/p/Throwable.class)
public Result handleException(Throwable throwable){
log.error("錯誤",throwable);
return Result.failed(400, "系統例外");
}
}
3. 測驗結果
{
"code": 400,
"data": "",
"msg": "logo:地址必須不為空 name:品牌名必須提交 "
}
八、分組校驗
1. 需求
我們在做校驗的時候,通常會遇到一個物體類的添加和修改,他們的校驗規則是不同的,所以分組顯得尤為重要,他可以幫助我們少建一個冗余的物體類,所以我們必須要會的,
2. 創建分組介面(不需寫任何內容)
public interface EditGroup {
}
public interface AddGroup {
}
3. 在需要二義性的欄位上添加分組
/**
* 品牌id
*/
@NotNull(message = "修改必須有品牌id",groups = {EditGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
private Long brandId;
// 其余屬性我們不變
4. 不同Controller添加校驗規則
注意:我們要進行分組,所以@Valid不能使用了,要使用@Validated,相信大家已經看到上面的他倆區別了哈!
@PostMapping("/add")
public Result add(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity){
return Result.success("成功");
}
@PostMapping("/edit")
public Result edit(@Validated({EditGroup.class}) @RequestBody BrandEntity brandEntity){
return Result.success("成功");
}
5. 測驗


九、自定義校驗
1.定義自定義校驗器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
//撰寫自定義的校驗器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<Integer>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = https://www.cnblogs.com/wang1221/p/constraintAnnotation.vals();
for (int i : value) {
set.add(i);
}
}
/**
* 判斷是否校驗成功
* @param value 需要校驗的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
2. 定義一個注解配合校驗器使用
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 使用該屬性去Validation.properties中取
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default {};
}
3. 物體類添加一個新的校驗屬性
注意:我們上面做了分組,如果屬性不指定分組,則不會生效,現在我們的部分屬性校驗已沒有起作用,現在只有brandId和showStatus起作用,
/**
* 顯示狀態[0-不顯示;1-顯示]
*/
@NotNull(groups = {AddGroup.class, EditGroup.class})
@ListValue(vals = {0,1},groups = {AddGroup.class, EditGroup.class},message = "必須為0或者1")
private Integer showStatus;
4. 測驗


十、總結
這樣就差不多對JSR303有了基本了解,滿足基本開發沒有什么問題哈!看到這里了,收藏點贊一波吧,整理了將近一天!!謝謝大家了!!
歡迎大家關注小編的微信公眾號!!

有緣人才能看到,自己網站,歡迎訪問!!!
點擊訪問!歡迎訪問,里面也是有很多好的文章哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/509283.html
標籤:Java
