javascript
学习Spring Boot:(十)使用hibernate validation完成数据后端校验
前言
后臺數(shù)據(jù)的校驗(yàn)也是開發(fā)中比較注重的一點(diǎn),用來校驗(yàn)數(shù)據(jù)的正確性,以免一些非法的數(shù)據(jù)破壞系統(tǒng),或者進(jìn)入數(shù)據(jù)庫,造成數(shù)據(jù)污染,由于數(shù)據(jù)檢驗(yàn)可能應(yīng)用到很多層面,所以系統(tǒng)對數(shù)據(jù)校驗(yàn)要求比較嚴(yán)格且追求可變性及效率。
了解
了解一點(diǎn)概念性的東東。
* JSR 303 是 Java 為 Bean 數(shù)據(jù)合法性校驗(yàn)提供的標(biāo)準(zhǔn)框架,它已經(jīng)包含在 JavaEE 6.0 中 。
* Hibernate Validator 是 JSR 303 的一個參考實(shí)現(xiàn),所以它多實(shí)現(xiàn)了幾個校驗(yàn)規(guī)則。
* Spring 4.0 擁有自己獨(dú)立的數(shù)據(jù)校驗(yàn)框架,同時支持 JSR303 標(biāo)準(zhǔn)的校驗(yàn)框架。
* 在已經(jīng)標(biāo)注了 JSR303 注解的表單/命令對象前標(biāo)注一個@Valid,Spring MVC 框架在將請求參數(shù)綁定到該入?yún)ο蠛?就會調(diào)用校驗(yàn)框架根據(jù)注解聲明的校驗(yàn)規(guī)則實(shí)施校驗(yàn)
* Spring MVC 是通過對處理方法簽名的規(guī)約來保存校驗(yàn)結(jié)果的:前一個表單/命令對象的校驗(yàn)結(jié)果保存到隨后的入?yún)⒅?這個保存校驗(yàn)結(jié)果的入?yún)⒈仨毷?BindingResult 或Errors 類型,這兩個類都位于org.springframework.validation 包中。
* 需校驗(yàn)的 Bean 對象和其綁定結(jié)果對象或錯誤對象時成對出現(xiàn)的,它們之間不允許聲明其他的入?yún)?
* Errors 接口提供了獲取錯誤信息的方法,如 getErrorCount() 或getFieldErrors(String field)
* BindingResult 擴(kuò)展了 Errors 接口。
支持的注解
JSR 提供的校驗(yàn)注解:
@Null 被的注解元素必須為 null @NotNull 被注解的元素必須不為 null @AssertTrue 被注解的元素必須為 true @AssertFalse 被注解的元素必須為 false @Min(value) 被注解的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 @Max(value) 被注解的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 @DecimalMin(value) 被注解的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 @DecimalMax(value) 被注解的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 @Size(max=, min=) 被注解的元素的大小必須在指定的范圍內(nèi) 集合或數(shù)組 集合或數(shù)組的大小是否在指定范圍內(nèi) @Digits (integer, fraction) 被注解的元素必須是一個數(shù)字,驗(yàn)證是否是符合指定格式的數(shù)字,interger指定整數(shù)精度,fraction指定小數(shù)精度。 @Past 被注解的元素必須是一個過去的日期 @Future 被注解的元素必須是一個將來的日期 @Pattern(regex=,flag=) 被注解的元素必須符合指定的正則表達(dá)式Hibernate Validator 提供的校驗(yàn)注解:
@NotBlank(message =) 驗(yàn)證字符串非null,且長度必須大于0 @Email 被注釋的元素必須是電子郵箱地址 @Length(min=,max=) 被注解的值大小必須在指定的范圍內(nèi) @NotEmpty 被注解的字符串的必須非空 @Range(min=,max=,message=) 驗(yàn)證該值必須在合適的范圍內(nèi)可以在需要驗(yàn)證的屬性上,使用多個驗(yàn)證方式,它們同時生效。
spring boot web 已經(jīng)有 hibernate-validation 的依賴,所以不需要再手動添加依賴。
使用
首先我在我的實(shí)體類上寫了幾個校驗(yàn)注解。
public class SysUserEntity implements Serializable {private static final long serialVersionUID = 1L;//主鍵private Long id;//用戶名@NotBlank(message = "用戶名不能為空", groups = {AddGroup.class, UpdateGroup.class})private String username;//密碼@NotBlank(message = "密碼不能為空", groups = {AddGroup.class})private String password;//手機(jī)號@Pattern(regexp = "^1([345789])\\d{9}$",message = "手機(jī)號碼格式錯誤")@NotBlank(message = "手機(jī)號碼不能為空")private String mobile;//郵箱@Email(message = "郵箱格式不正確")private String email;//創(chuàng)建者private Long createUserId;//創(chuàng)建時間private Date createDate; // ignore set and get使用@Validated進(jìn)行校驗(yàn)
首先了解下:
關(guān)于@Valid和@Validated的區(qū)別聯(lián)系
* @Valid: javax.validation, 是javax,也是就是jsr303中定義的規(guī)范注解
* @Validated: org.springframework.validation.annotation, 是spring自己封裝的注解。參數(shù)校驗(yàn)失敗拋出 org.springframework.validation.BindException 異常。
@Validated 是 @Valid 的一個變種,擴(kuò)展了 @Valid 的功能,支持 group分組校驗(yàn) 的寫法,所以為了校驗(yàn)統(tǒng)一,盡量使用 @Validated
在controller自定義一個接口
@PostMapping("/valid")public ResponseEntity<String> valid(@Validated @RequestBody SysUserEntity user, BindingResult result) {if (result.hasErrors()) {return ResponseEntity.status(BAD_REQUEST).body("校驗(yàn)失敗");}return ResponseEntity.status(OK).body("校驗(yàn)成功");}需要注意的有幾點(diǎn):
* 需要校驗(yàn)對象的時候,需要加上 spring 的校驗(yàn)注解 @Validated ,表示我們需要 spring 對它進(jìn)行校驗(yàn),而校驗(yàn)的信息會存放到其后的BindingResult中。
* BindingResult 必須和檢驗(yàn)對象緊鄰,中間不能穿插任何參數(shù),如果有多個校驗(yàn)對象 @Validated @RequestBody SysUserEntity user, BindingResult result, @Validated @RequestBody SysUserEntity user1, BindingResult result1。
我在前端用 Swagger 進(jìn)行測試下。
我發(fā)送一個 body,將 手機(jī)號輸錯:
后端調(diào)試下 BindingResult 的結(jié)果,發(fā)現(xiàn)結(jié)果:
只要注意下 errors 屬性,它是校驗(yàn)所有不符合規(guī)則的,是一個數(shù)組。
分組校驗(yàn)
有時候 ,我們在新增和更新的時候校驗(yàn)效果是不一樣的。例如上面,我在User新增的時候需要判斷密碼是不是為空,但是更新的時候我不做校驗(yàn)。這個時候就也要用到分組校驗(yàn)了。
@NotBlank(message = "密碼不能為空", groups = {AddGroup.class}) private String password;將Contoller中的校驗(yàn)修改下。
(@Validated({AddGroup.class}) @RequestBody SysUserEntity user, BindingResult result)上面的意思是只有分組是AddGroup的校驗(yàn)才生效,其余的校驗(yàn)忽略。
經(jīng)過我測試,把分組情況分下:
1. 在controller校驗(yàn)沒加分組的時候,只對實(shí)體類的沒有分組的注解有效。
2. 在controller校驗(yàn)加分組的時候,只對實(shí)體類的當(dāng)前分組的注解有效,沒有注解的也無效。
3. 當(dāng)校驗(yàn)有兩個分組的時候@Validated({AddGroup.class, UpdateGroup.class}),滿足當(dāng)前兩個分組其中任意一個都可以校驗(yàn),兩個注解同時一起出現(xiàn),也沒問題,而且檢驗(yàn)不通過的信息不會重復(fù)。
自定義校驗(yàn)
有時候系統(tǒng)提供給我們的校驗(yàn)注解,并不夠用,我們可以自定義校驗(yàn),來滿足我們的業(yè)務(wù)需求。
例如:現(xiàn)在我們有一個需求,需要檢測一條信息的敏感詞匯,如sb ……文明人,舉個栗子 ……
自定義校驗(yàn)注解
// 注解可以用在哪些地方 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented // 指定校驗(yàn)規(guī)則實(shí)現(xiàn)類 @Constraint(validatedBy = {NotHaveSBValidator.class}) public @interface NotHaveSB {//默認(rèn)錯誤消息String message() default "不能包含字符sb";//分組Class<?>[] groups() default {};//負(fù)載Class<? extends Payload>[] payload() default {};//指定多個時使用@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})@Retention(RUNTIME)@Documented@interface List {NotHaveSB[] value();}}規(guī)則校驗(yàn)實(shí)現(xiàn)類
// 可以指定檢驗(yàn)類型,這里選擇的是 String public class NotHaveSBValidator implements ConstraintValidator<NotHaveSB, String> {@Overridepublic void initialize(NotHaveSB notHaveSB) {}/**** @param s 待檢驗(yàn)對象* @param constraintValidatorContext 檢驗(yàn)上下文,可以設(shè)置檢驗(yàn)的錯誤信息* @return false 代表檢驗(yàn)失敗*/@Overridepublic boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {return !StringUtils.isNotBlank(s) || !s.toLowerCase().contains("sb");} }所有的驗(yàn)證者都需要實(shí)現(xiàn)ConstraintValidator接口,它的接口也很形象,包含一個初始化事件方法,和一個判斷是否合法的方法。
測試一下喂
現(xiàn)在我的用戶類上,也沒什么多余的字段拿出來測試,暫時把 password 字段拿來測試吧。
//@NotBlank(message = "密碼不能為空", groups = AddGroup.class)@NotHaveSBprivate String password;手動校驗(yàn)
這個是我最終想要的處理方式。
由于現(xiàn)在都是前后端分離開發(fā)的,校驗(yàn)失敗的時候,拋出自定義的異常,然后統(tǒng)一處理這些異常,最后將相關(guān)的錯誤提示信息返回給前端處理。
新建一個驗(yàn)證工具類
public class ValidatorUtils {private static Validator validator;static {validator = Validation.buildDefaultValidatorFactory().getValidator();}/*** 手動校驗(yàn)對象** @param object 待校驗(yàn)對象* @param groups 待校驗(yàn)的組* @throws KCException 校驗(yàn)不通過,則拋出 KCException 異常*/public static void validateEntity(Object object, Class<?>... groups)throws KCException {Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);if (!constraintViolations.isEmpty()) {String msg = constraintViolations.parallelStream().map(ConstraintViolation::getMessage).collect(Collectors.joining(","));throw new KCException(msg);}} }它主要做的事情就是驗(yàn)證我們的待驗(yàn)證對象,驗(yàn)證不同通過的時候,拋出自定義異常,在后臺統(tǒng)一處理異常就可以了。
在業(yè)務(wù)中直接調(diào)用就可以了,有分組添加分組就行:
@PostMapping("/valid1")public ResponseEntity<String> customValid(@RequestBody SysUserEntity user) {ValidatorUtils.validateEntity(user);return ResponseEntity.status(OK).body("校驗(yàn)成功");}最后測試一下,查看返回結(jié)果是否符合預(yù)期:
手動校驗(yàn)的補(bǔ)充
決定還是采用注解的形式進(jìn)行編碼,本來想用處理方法參數(shù)的裝配進(jìn)行檢驗(yàn),寫好了發(fā)現(xiàn)和 @responseBody 不能同時使用,然后發(fā)現(xiàn)還是可以使用 @Validated 直接校驗(yàn),拋出異常, 進(jìn)行捕捉異常統(tǒng)一處理。
@PostMapping()@ApiOperation("新增")public ResponseEntity insert(@Validated SysUserAddForm user)在全局異常處理里面加上 處理綁定參數(shù)異常 org.springframework.validation.BindException:
/*** 參數(shù)檢驗(yàn)違反約束(數(shù)據(jù)校驗(yàn))* @param e BindException* @return error message*/@org.springframework.web.bind.annotation.ExceptionHandler(BindException.class)public ResponseEntity<String> handleConstraintViolationException(BindException e) {LOGGER.debug(e.getMessage(), e);return ResponseEntity.status(BAD_REQUEST).body(e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")));}轉(zhuǎn)載于:https://www.cnblogs.com/qnight/p/8997498.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的学习Spring Boot:(十)使用hibernate validation完成数据后端校验的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vmware克隆虚拟机
- 下一篇: SpringBoot使用Gradle构建