springboot整合JSR303參數(shù)校驗與全局異常處理的方法
一、前言
我們在日常開發(fā)中,避不開的就是參數(shù)校驗,有人說前端不是會在表單中進(jìn)行校驗的嗎?在后端中,我們可以直接不管前端怎么樣判斷過濾,我們后端都需要進(jìn)行再次判斷,為了安全
。因為前端很容易拜托,當(dāng)測試使用PostMan
來測試,如果后端沒有校驗,不就亂了嗎?肯定會有很多異常的。今天小編和大家一起學(xué)習(xí)一下JSR303專門用于參數(shù)校驗的,算是一個工具吧!
二、JSR303簡介
JSR-303 是 JAVA EE 6 中的一項子規(guī)范,叫做 Bean Validation,官方參考實現(xiàn)是Hibernate Validator。
Hibernate Validator 提供了 JSR 303 規(guī)范中所有內(nèi)置 constraint 的實現(xiàn),除此之外還有一些附加的 constraint。
官網(wǎng)介紹:
驗證數(shù)據(jù)是一項常見任務(wù),它發(fā)生在從表示層到持久層的所有應(yīng)用程序?qū)又?。通常在每一層都實現(xiàn)相同的驗證邏輯,這既耗時又容易出錯。為了避免重復(fù)這些驗證,開發(fā)人員經(jīng)常將驗證邏輯直接捆綁到域模型中,將域類與驗證代碼混在一起,而驗證代碼實際上是關(guān)于類本身的元數(shù)據(jù)。
Jakarta Bean Validation 2.0 - 為實體和方法驗證定義了元數(shù)據(jù)模型和 API。默認(rèn)元數(shù)據(jù)源是注釋,能夠通過使用 XML 覆蓋和擴(kuò)展元數(shù)據(jù)。API 不依賴于特定的應(yīng)用程序?qū)踊蚓幊棠P?。它特別不依賴于 Web 或持久層,并且可用于服務(wù)器端應(yīng)用程序編程以及富客戶端 Swing 應(yīng)用程序開發(fā)人員。
三、導(dǎo)入依賴
<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)長度是否在給定的范圍之內(nèi) |
@Length | 用于String對象的大小必須在指定的范圍內(nèi) |
@Pattern | 用于String對象是否符合正則表達(dá)式的規(guī)則 |
用于String對象是否符合郵箱格式 | |
@Min | 用于Number和String對象是否大等于指定的值 |
@Max | 用于Number和String對象是否小等于指定的值 |
@AssertTrue | 用于Boolean對象是否為true |
@AssertFalse | 用于Boolean對象是否為false |
所有的大家參考jar包
五、@Validated、@Valid區(qū)別
@Validated:
- Spring提供的
- 支持分組校驗
- 可以用在類型、方法和方法參數(shù)上。但是不能用在成員屬性(字段)上
- 由于無法加在成員屬性(字段)上,所以無法單獨完成級聯(lián)校驗,需要配合@Valid
@Valid:
- JDK提供的(標(biāo)準(zhǔn)JSR-303規(guī)范)
- 不支持分組校驗
- 可以用在方法、構(gòu)造函數(shù)、方法參數(shù)和成員屬性(字段)上
- 可以加在成員屬性(字段)上,能夠獨自完成級聯(lián)校驗
總結(jié):@Validated用到分組時使用,一個學(xué)校對象里還有很多個學(xué)生對象需要使用@Validated在Controller方法參數(shù)前加上,@Valid加在學(xué)校中的學(xué)生屬性上,不加則無法對學(xué)生對象里的屬性進(jìn)行校驗!
區(qū)別參考博客地址
例子:
@Data public class School{ @NotBlank private String id; private String name; @Valid // 需要加上,否則不會驗證student類中的校驗注解 @NotNull // 且需要觸發(fā)該字段的驗證才會進(jìn)行嵌套驗證。 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; /** * 檢索首字母 */ //正則表達(dá)式 @Pattern(regexp = "^[a-zA-Z]$",message = "檢索的首字母必須是字母") private String firstLetter; /** * 排序 */ @Min(value = 0,message = "排序必須大于等于0") private Integer sort; }
2. 統(tǒng)一返回類型
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; //統(tǒng)一返回結(jié)果 @Data @NoArgsConstructor @AllArgsConstructor @ApiModel public class Result<T> { @ApiModelProperty("響應(yīng)碼") private Integer code; @ApiModelProperty("相應(yīng)信息") 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 = "系統(tǒng)異常,請聯(lián)系管理員"; //沒有權(quán)限的響應(yīng)碼 public static final Integer NO_AUTH_COOD = 999; //執(zhí)行成功 public static <T> Result<T> success(T data){ return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data); } //執(zhí)行失敗 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,""); } //傳入錯誤碼的數(shù)據(jù) 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("成功"); }
遇到的坑:小編在公司的項目中添加沒什么問題,但是就是無法觸發(fā)校驗,看到的是Springboot版本太高了
,所有要添加下面的依賴才觸發(fā)。
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.18.Final</version> </dependency>
4. 普通測試結(jié)果
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,"提交的數(shù)據(jù)不合規(guī)范",map); } return Result.success("成功"); }
6. 異常處理結(jié)果
{ "code": 400, "data": { "name": "品牌名必須提交", "logo": "地址必須不為空" }, "msg": "提交的數(shù)據(jù)不合規(guī)范" }
七、抽離全局異常處理
1. 心得體會
上面我們要在每個校驗的接口上面寫,所以我們要抽離出來做個全局異常。并且要改進(jìn)一下,原來的是把錯誤信息放到data里,但是正常情況下的data是返回給前端的數(shù)據(jù)。我們這樣把異常數(shù)據(jù)放進(jìn)去,會使data的數(shù)據(jù)有二義性
。這樣對于前端就不知道里面是數(shù)據(jù)還是報錯信息了哈,這樣就可以直接前端展示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 = MethodArgumentNotValidException.class) public Result handleVaildException(MethodArgumentNotValidException e){ log.error("數(shù)據(jù)校驗出現(xiàn)問題:{},異常類型:{}",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 = Throwable.class) public Result handleException(Throwable throwable){ log.error("錯誤",throwable); return Result.failed(400, "系統(tǒng)異常"); } }
3. 測試結(jié)果
{
"code": 400,
"data": "",
"msg": "logo:地址必須不為空 name:品牌名必須提交 "
}
八、分組校驗
1. 需求
我們在做校驗的時候,通常會遇到一個實體類的添加和修改,他們的校驗規(guī)則是不同的,所以分組顯得尤為重要。他可以幫助我們少建一個冗余的實體類,所以我們必須要會的。
2. 創(chuàng)建分組接口(不需寫任何內(nèi)容)
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添加校驗規(guī)則
注意:我們要進(jìn)行分組,所以@Valid
不能使用了,要使用@Validated
。相信大家已經(jīng)看到上面的他倆區(qū)別了哈!
@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 = 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. 實體類添加一個新的校驗屬性
注意:我們上面做了分組,如果屬性不指定分組,則不會生效,現(xiàn)在我們的部分屬性校驗已沒有起作用,現(xiàn)在只有brandId和showStatus
起作用。
/** * 顯示狀態(tài)[0-不顯示;1-顯示] */ @NotNull(groups = {AddGroup.class, EditGroup.class}) @ListValue(vals = {0,1},groups = {AddGroup.class, EditGroup.class},message = "必須為0或者1") private Integer showStatus;
4. 測試
十、總結(jié)
這樣就差不多對JSR303有了基本了解,滿足基本開發(fā)沒有什么問題哈!看到這里了,收藏點贊一波吧,整理了將近一天?。≈x謝大家了??!
到此這篇關(guān)于springboot整合JSR303參數(shù)校驗與全局異常處理的文章就介紹到這了,更多相關(guān)springboot JSR303參數(shù)校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot 多數(shù)據(jù)源及事務(wù)解決方案小結(jié)
本文主要介紹了多數(shù)據(jù)源管理的解決方案(應(yīng)用層事務(wù),而非XA二段提交保證),以及對多個庫同時操作的事務(wù)管理,具有一定的參考價值,感興趣的可以了解一下2024-06-06SpringBoot集成redis實現(xiàn)分布式鎖的示例代碼
這篇文章主要介紹了SpringBoot集成redis實現(xiàn)分布式鎖的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java創(chuàng)建可執(zhí)行JAR文件的多種方式
本文主要介紹了Java創(chuàng)建可執(zhí)行JAR文件的多種方式,使用JDK的jar工具、IDE、Maven和Gradle來創(chuàng)建和配置可執(zhí)行JAR文件,具有一定的參考價值,感興趣的可以了解一下2024-07-07SpringBoot中Controller參數(shù)與返回值的用法總結(jié)
這篇文章主要介紹了SpringBoot中Controller參數(shù)與返回值的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07java中的數(shù)學(xué)計算函數(shù)的總結(jié)
這篇文章主要介紹了java中的數(shù)學(xué)計算函數(shù)的總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-07-07