您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)Spring Boot 中怎么利用JSR303 實(shí)現(xiàn)參數(shù)驗(yàn)證,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
引入依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
給參數(shù)對(duì)象添加校驗(yàn)注解
@Data public class User { private Integer id; @NotBlank(message = "用戶(hù)名不能為空") private String username; @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密碼必須為8~16個(gè)字母和數(shù)字組合") private String password; @Email private String email; private Integer gender; }
Controller 中需要校驗(yàn)的參數(shù)Bean前添加 @Valid 開(kāi)啟校驗(yàn)功能,緊跟在校驗(yàn)的Bean后添加一個(gè)BindingResult,BindingResult封裝了前面Bean的校驗(yàn)結(jié)果。
@RestController @RequestMapping("/user") public class UserController { @PostMapping("") public Result save (@Valid User user , BindingResult bindingResult) { if (bindingResult.hasErrors()) { Map<String , String> map = new HashMap<>(); bindingResult.getFieldErrors().forEach( (item) -> { String message = item.getDefaultMessage(); String field = item.getField(); map.put( field , message ); } ); return Result.build( 400 , "非法參數(shù) !" , map); } return Result.ok(); } }
測(cè)試如下:
參數(shù)校驗(yàn)不通過(guò)時(shí),會(huì)拋出 BingBindException 異常,可以在統(tǒng)一異常處理中,做統(tǒng)一處理,這樣就不用在每個(gè)需要參數(shù)校驗(yàn)的地方都用 BindingResult 獲取校驗(yàn)結(jié)果了。
@Slf4j @RestControllerAdvice(basePackages = "com.itwolfed.controller") public class GlobalExceptionControllerAdvice { @ExceptionHandler(value= {MethodArgumentNotValidException.class , BindException.class}) public Result handleVaildException(Exception e){ BindingResult bindingResult = null; if (e instanceof MethodArgumentNotValidException) { bindingResult = ((MethodArgumentNotValidException)e).getBindingResult(); } else if (e instanceof BindException) { bindingResult = ((BindException)e).getBindingResult(); } Map<String,String> errorMap = new HashMap<>(16); bindingResult.getFieldErrors().forEach((fieldError)-> errorMap.put(fieldError.getField(),fieldError.getDefaultMessage()) ); return Result.build(400 , "非法參數(shù) !" , errorMap); } }
新增和修改對(duì)于實(shí)體的校驗(yàn)規(guī)則是不同的,例如id是自增的時(shí),新增時(shí)id要為空,修改則必須不為空;新增和修改,若用的恰好又是同一種實(shí)體,那就需要用到分組校驗(yàn)。
校驗(yàn)注解都有一個(gè)groups屬性,可以將校驗(yàn)注解分組,我們看下 @NotNull的源碼:
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Repeatable(List.class) @Documented @Constraint(validatedBy = { }) public @interface NotNull { String message() default "{javax.validation.constraints.NotNull.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented @interface List { NotNull[] value(); } }
從源碼可以看出 groups 是一個(gè)Class<?>類(lèi)型的數(shù)組,那么就可以創(chuàng)建一個(gè)Groups.
public class Groups { public interface Add{} public interface Update{} }
給參數(shù)對(duì)象的校驗(yàn)注解添加分組
@Data public class User { @Null(message = "新增不需要指定id" , groups = Groups.Add.class) @NotNull(message = "修改需要指定id" , groups = Groups.Update.class) private Integer id; @NotBlank(message = "用戶(hù)名不能為空") @NotNull private String username; @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密碼必須為8~16個(gè)字母和數(shù)字組合") private String password; @Email private String email; private Integer gender; }
Controller 中原先的 @Valid不能指定分組 ,需要替換成 @Validated
@RestController @RequestMapping("/user") public class UserController { @PostMapping("") public Result save (@Validated(Groups.Add.class) User user) { return Result.ok(); } }
測(cè)試如下:
雖然JSR303和springboot-validator 已經(jīng)提供了很多校驗(yàn)注解,但是當(dāng)面對(duì)復(fù)雜參數(shù)校驗(yàn)時(shí),還是不能滿(mǎn)足我們的要求,這時(shí)候我們就需要 自定義校驗(yàn)注解。
例如User中的gender,用 1代表男 2代表女,我們自定義一個(gè)校驗(yàn)注解 @ListValue,指定取值只能1和2。
@Documented @Constraint(validatedBy = { ListValueConstraintValidator.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE }) @Retention(RUNTIME) public @interface ListValue { String message() default ""; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; int[] vals() default { }; }
一個(gè)標(biāo)注(annotation) 是通過(guò)
@interface關(guān)鍵字來(lái)定義的. 這個(gè)標(biāo)注中的屬性是聲明成類(lèi)似方法
的樣式的. 根據(jù)Bean Validation API 規(guī)范的要求:
message屬性, 這個(gè)屬性被用來(lái)定義默認(rèn)得消息模版, 當(dāng)這個(gè)約束條件被驗(yàn)證失敗的時(shí)候,通過(guò)
此屬性來(lái)輸出錯(cuò)誤信息。
groups 屬性, 用于指定這個(gè)約束條件屬于哪(些)個(gè)校驗(yàn)組. 這個(gè)的默認(rèn)值必須是Class<?>類(lèi)型數(shù)組。
payload 屬性, Bean Validation API 的使用者可以通過(guò)此屬性來(lái)給約束條件指定嚴(yán)重級(jí)別. 這個(gè)屬性并不被API自身所使用。
除了這三個(gè)強(qiáng)制性要求的屬性(message, groups 和 payload) 之外, 我們還添
加了一個(gè)屬性用來(lái)指定所要求的值. 此屬性的名稱(chēng)vals在annotation的定義中比較特
殊, 如果只有這個(gè)屬性被賦值了的話(huà), 那么, 在使用此annotation到時(shí)候可以忽略此屬性名稱(chēng).
另外, 我們還給這個(gè)annotation標(biāo)注了一些元標(biāo)注( meta
annotatioins):
@Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者
annotation聲明上。
@Retention(RUNTIME): 表示這個(gè)標(biāo)注信息是在運(yùn)行期通過(guò)反射被讀取的.
@Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪個(gè)校驗(yàn)器(類(lèi)) 去校驗(yàn)使用了此標(biāo)注的元素.
@Documented: 表示在對(duì)使用了該注解的類(lèi)進(jìn)行javadoc操作到時(shí)候, 這個(gè)標(biāo)注會(huì)被添加到
javadoc當(dāng)中.
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<>(); /** * 初始化方法 */ @Override public void initialize(ListValue constraintAnnotation) { int[] vals = constraintAnnotation.vals(); for (int val : vals) { set.add(val); } } /** * 判斷是否校驗(yàn)成功 * * @param value 需要校驗(yàn)的值 * @param context * @return */ @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { return set.contains(value); } }
ListValueConstraintValidator定義了兩個(gè)泛型參數(shù), 第一個(gè)是這個(gè)校驗(yàn)器所服務(wù)到標(biāo)注類(lèi)型(在我們的例子中即ListValue), 第二個(gè)這個(gè)校驗(yàn)器所支持到被校驗(yàn)元素的類(lèi)型 (即Integer)。
如果一個(gè)約束標(biāo)注支持多種類(lèi)型的被校驗(yàn)元素的話(huà), 那么需要為每個(gè)所支持的類(lèi)型定義一個(gè)ConstraintValidator,并且注冊(cè)到約束標(biāo)注中。
這個(gè)驗(yàn)證器的實(shí)現(xiàn)就很平常了, initialize() 方法傳進(jìn)來(lái)一個(gè)所要驗(yàn)證的標(biāo)注類(lèi)型的實(shí)例, 在本
例中, 我們通過(guò)此實(shí)例來(lái)獲取其vals屬性的值,并將其保存為Set集合中供下一步使
用。
isValid()是實(shí)現(xiàn)真正的校驗(yàn)邏輯的地方, 判斷一個(gè)給定的int對(duì)于
@ListValue這個(gè)約束條件來(lái)說(shuō)
是否是合法的。
在參數(shù)對(duì)象中使用 @ListValue注解。
@Data public class User { @Null(message = "新增不需要指定id" , groups = Groups.Add.class) @NotNull(message = "修改需要指定id" , groups = Groups.Update.class) private Integer id; @NotBlank(message = "用戶(hù)名不能為空") @NotNull private String username; @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密碼必須為8~16個(gè)字母和數(shù)字組合") private String password; @Email private String email; @ListValue( message = "性別應(yīng)指定相應(yīng)的值" , vals = {1,2} , groups = {Groups.Add.class , Groups.Update.class}) private Integer gender; }
測(cè)試如下:
看完上述內(nèi)容,你們對(duì)Spring Boot 中怎么利用JSR303 實(shí)現(xiàn)參數(shù)驗(yàn)證有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。