溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在springboot中利用hibernate validation對參數(shù)進行校驗

發(fā)布時間:2020-12-30 14:42:01 來源:億速云 閱讀:178 作者:Leah 欄目:開發(fā)技術(shù)

這篇文章給大家介紹怎么在springboot中利用hibernate validation對參數(shù)進行校驗,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

springboot天生支持使用hibernate validation對參數(shù)的優(yōu)雅校驗,如果不使用它,只能對參數(shù)挨個進行如下方式的手工校驗,不僅難看,使用起來還很不方便:

if(StringUtils.isEmpty(userName)){
	throw new RuntimeException("用戶名不能為空");
}

下面將介紹hibernate validation的基本使用方法。

一、引入依賴

這里在springboot 2.4.1中進行實驗,引入以下依賴:

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.4.1</version>
 <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.16</version>
 </dependency>
 <dependency>
 <groupId>org.hibernate.validator</groupId>
 <artifactId>hibernate-validator</artifactId>
 <version>6.1.6.Final</version>
 </dependency>
</dependencies>

二、基本請求參數(shù)校驗

如下的一個spring mvc的請求調(diào)用中有一個id參數(shù)(Integer類型),如果不允許它為空,該怎么做

  • 在Controller上加上@Validated注解

  • 在需要校驗的字段前面加上@NotNull(message = "用戶id不能為空")注解

  • 定義全局異常處理類,定制化返回結(jié)果

 @RestControllerAdvice
 @Slf4j
 public class ValidationAdvice {
 
 @ExceptionHandler(Exception.class)
 @ResponseBody
 public WrapperResult handler(Exception e) {
  //獲取異常信息,獲取異常堆棧的完整異常信息
  StringWriter sw = new StringWriter();
  PrintWriter pw = new PrintWriter(sw);
  e.printStackTrace(pw);
  //日志輸出異常詳情
  log.error(sw.toString());
  return WrapperResult.faild("服務異常,請稍后再試");
 }
 
 @ExceptionHandler(ConstraintViolationException.class)
 @ResponseBody
 public WrapperResult handler(ConstraintViolationException e) {
  StringBuffer errorMsg = new StringBuffer();
  Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
  violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
  return WrapperResult.faild(errorMsg.toString());
 }
 }

Controller層代碼如下所示:

 @RestController
 @Slf4j
 @RequestMapping("/user")
 @Validated
 public class UserController {
 
 /**
 * 根據(jù)id查詢用戶信息
 *
 * @param id
 * @return
 */
 @GetMapping
 public WrapperResult<UserModel> findUser(@NotNull(message = "用戶id不能為空")
      @RequestParam(value = "id")
      String id) {
  return WrapperResult.success(new UserModel());
 }
 }

如果發(fā)起請求127.0.0.1:8080/user?id= 則會返回結(jié)果

{
 "status": 1,
 "data": "用戶id不能為空;",
 "msg": "FAIL",
 "success": false
 }

三、對象內(nèi)參數(shù)校驗

上面是GET請求,下面介紹POST請求,請求對象內(nèi)的參數(shù)校驗。

1.Controller類上加上@Validated注解

@RestController
@Slf4j
@RequestMapping("/user")
**@Validated**
public class UserController {
}

2.在POST請求方法參數(shù)前面加上@Validated 注解

 @PostMapping("/mobile-regist")
 public WrapperResult<Boolean> mobileRegit(@Validated @RequestBody UserModel userModel) {
 return WrapperResult.success(true);
 }

3.在上面介紹的ValidationAdvice類中加上對象參數(shù)校驗異常捕獲

//處理校驗異常,對于對象類型的數(shù)據(jù)的校驗異常
 @ExceptionHandler(MethodArgumentNotValidException.class)
 @ResponseBody
 public WrapperResult handler(MethodArgumentNotValidException e) {
 StringBuffer sb = new StringBuffer();
 List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
 allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
 return WrapperResult.faild(sb.toString());
 }

UserModel類的定義如下:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {

 @NotEmpty(message = "姓名不能為空")
 private String name;

 @NotEmpty(message = "手機號不能為空")
// @Mobile(message = "手機號格式不正確")
 private String mobile;

 @NotEmpty(message = "電子郵箱不能為空")
	@Email(message = "電子郵箱格式不正確")
 private String email;

 private String password;

 private String address;

 @NotNull(message = "年齡不能為空")
 @Min(value = 12, message = "允許注冊年齡最小為12歲")
 @Max(value = 24, message = "允許年齡最大為24歲")
 private Integer age;

 @NotEmpty(message = "聯(lián)系人不允許為空")
 @Size(min = 1, max = 3, message = "聯(lián)系人長度只允許1到3之間")
 private List<String> contacts;
}

如果POST請求如下所示

{
 "name":"",
 "mobile":"12666666666",
 "email":"",
 "password":"",
 "address":"",
 "age": null,
 "contacts":[

 ]
}

則會返回如下定制化返回結(jié)果:

{
 "status": 1,
 "data": "電子郵箱不能為空;聯(lián)系人長度只允許1到3之間;年齡不能為空;聯(lián)系人不允許為空;姓名不能為空;手機號格式不正確;",
 "msg": "FAIL",
 "success": false
}

四、自定義校驗器

像是@NotNull、@Email等注解都是hibernate validation 內(nèi)置的注解,我們想開發(fā)像是@Email注解一樣功能的注解,如何做呢,比如@Mobile,它的使用方法將和@Email一模一樣。

首先,先定義一個工具類存放ValidationUtil兩個常量值

public class ValidationUtil {
 //手機號校驗正則
 public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";

 public static final String MOBILE_MSG = "手機號格式錯誤";
}

1.定義注解Mobile

具體代碼可以參考@Email的實現(xiàn),直接將Email名字改成Mobile即可,如下所示:

@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Mobile {

 String message() default ValidationUtil.MOBILE_MSG;

 Class<?>[] groups() default {};

 Class<? extends Payload>[] payload() default {};

 String regexp() default ValidationUtil.MOBILE_REGX;

 Pattern.Flag[] flags() default {};

 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
 @Retention(RUNTIME)
 @Documented
 public @interface List {
 Mobile[] value();
 }
}

2.定義MobileValidator實現(xiàn)對參數(shù)的校驗邏輯

public class MobileValidator implements ConstraintValidator<Mobile, String> {

 private String regexp;

 @Override
 public void initialize(Mobile constraintAnnotation) {
 //獲取校驗的手機號的格式
 this.regexp = constraintAnnotation.regexp();
 }

 @Override
 public boolean isValid(String value, ConstraintValidatorContext context) {
 if (!StringUtils.hasText(value)) {
  return true;
 }
 return value.matches(regexp);
 }
}

3.使用方法和@Email一模一樣

不贅述

五、分組校驗

假設一個用戶注冊的場景,用戶注冊有三種方式

  • 用戶名+圖形驗證碼注冊

  • 郵箱+郵箱驗證碼注冊

  • 手機號+短信驗證碼注冊

用戶注冊的時候除了方式不一樣,其他用戶信息基本相同,后端開了三個接口對應著著三種注冊方式,請求體中我們使用一個Model封裝了以上所有信息,包含著用戶名、郵箱、手機號等信息,這時候不同的接口被調(diào)用,model中需要校驗的參數(shù)就不一樣了:

用戶名注冊的時候郵箱地址和手機號可以為空,但是用戶名不能為空;通過郵箱注冊的時候,郵箱地址不能為空,但是用戶名和手機號可以為空;......

分組校驗專門應對這種情況。

1.首先定義三個接口,表示三種組類別

public interface ValidEmail {
}

public interface ValidMobile {
}

public interface ValidUserName {
}

2.在UserModel實體類上指名組類別

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {

 @NotEmpty(message = "姓名不能為空", groups = {ValidUserName.class})
 @UserName(groups = {ValidUserName.class})
 private String name;

 @NotEmpty(message = "手機號不能為空", groups = {ValidMobile.class})
 @Mobile(groups = {ValidMobile.class})
 private String mobile;

 @NotEmpty(message = "電子郵箱不能為空", groups = {ValidEmail.class})
 @Email(message = "電子郵箱格式不正確", groups = {ValidEmail.class})
 private String email;

 private String password;

 private String address;

 @NotNull(message = "年齡不能為空")
 @Min(value = 12, message = "允許注冊年齡最小為12歲", groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 @Max(value = 24, message = "允許年齡最大為24歲",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 private Integer age;

 @NotEmpty(message = "聯(lián)系人不允許為空",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 @Size(min = 1, max = 3, message = "聯(lián)系人長度只允許1到3之間",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 private List<String> contacts;
}

3.Controller方法上指名驗證組別

 /**
 * 手機號注冊
 *
 * @param userModel
 * @return
 */
 @PostMapping("/mobile-regist")
 public WrapperResult<Boolean> mobileRegit(@Validated(ValidMobile.class) @RequestBody UserModel userModel) {
 return WrapperResult.success(true);
 }

這時候進行如下請求:

POST http://127.0.0.1:8080/user/mobile-regist

{
 "mobile":"12666666666",
 "password":"",
 "address":"",
 "age": null,
 "contacts":[

 ]

}

則會返回結(jié)果:

{
"status": 1,
"data": "聯(lián)系人長度只允許1到3之間;手機號格式錯誤;聯(lián)系人不允許為空;",
"msg": "FAIL",
"success": false
}

該請求中并沒有傳遞email和username字段,而且結(jié)果中也未校驗出這兩個字段,符合預期結(jié)果。

六、手動校驗

此處的手動校驗并非是使用if/else進行簡單的手動校驗,而是使用Validation自帶的校驗工具對使用了@NotNull等注解的實體對象進行屬性校驗。

首先先獲取Valiation對象:

private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

1. 全屬性校驗

/**
 * 驗證某個對象所有字段
 *
 * @param obj
 * @param <T>
 * @return
 */
public static <T> ValidationResult validateEntity(T obj) {
 ValidationResult result = new ValidationResult();
 Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
 if (!CollectionUtils.isEmpty(set)) {
 result.setHasErrors(true);
 Map<String, String> errorMsg = new HashMap<>();
 for (ConstraintViolation<T> cv : set) {
  errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
 }
 result.setErrorMsg(errorMsg);
 }
 return result;
}

2.某個字段的單獨校驗

/**
* 驗證某個對象某個字段
*
* @param obj
* @param propertyName
* @param <T>
* @return
*/
public static <T> ValidationResult validateProperty(T obj, String propertyName) {
 ValidationResult result = new ValidationResult();
 Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);
 if (!CollectionUtils.isEmpty(set)) {
 result.setHasErrors(true);
 Map<String, String> errorMsg = new HashMap<>();
 for (ConstraintViolation<T> cv : set) {
  errorMsg.put(propertyName, cv.getMessage());
 }
 result.setErrorMsg(errorMsg);
 }
 return result;
}

ValidationResult的定義如下:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class ValidationResult {
 private Boolean hasErrors;
 private Map<String, String> errorMsg;
}

七、文件上傳校驗

1.tomcat容器下文件上傳校驗

在springboot+tomcat架構(gòu)下的文件上傳校驗,假如已經(jīng)有了如下的配置:

spring:
 servlet:
 multipart:
 max-file-size: 1MB
 max-request-size: 1MB

這表示只允許上傳小于1MB大小的文件,如果不指定異常處理器,默認會報前端400,在ValidationAdvice類中添加如下代碼可以自定義返回結(jié)果:

//文件上傳文件大小超出限制
 @ExceptionHandler(MaxUploadSizeExceededException.class)
 @ResponseBody
 public WrapperResult<Map<String,Object>> fileSizeException(MaxUploadSizeExceededException exception) {
 log.error("文件太大,上傳失敗",exception);
 return WrapperResult.faild("只允許上傳不大于"+exception.getMaxUploadSize()+"的文件");
 }

2.其它容器

在Jetty容器中1中的方法可能會失效,未驗證;在undertow容器中是一定會失效,已經(jīng)驗證。undertow容器畢竟和spring-boot沒有完全打磨好,不建議現(xiàn)階段使用。

八、附錄

1.所有校驗規(guī)則注解說明


注解說明
@Null被注解的元素必須為空
@NotNull被注解的元素必須不為空
@AssertTrue被注解的元素必須為true
@AssertFlase被注解的元素必須為false
@Min(value)被注解的元素必須是數(shù)字,且必須大于指定的最小值
@Max(value)被注解的元素必須是數(shù)字,且必須小于指定的最大值
@DecimalMin(value)被注解的元素必須是數(shù)字,且必須大于指定的最小值
@DecaimalMax(value)被注解的元素必須是數(shù)字,且必須小于指定的最大值
@Size(max=,min=)被注解元素的大小必須在指定的范圍內(nèi)
@Digit(integer,fraction)被注解元素必須是數(shù)字,且其值必須在可接受的范圍內(nèi)
@Past被注解元素必須是一個過去的日期
@Futrue被注解元素必須是一個將來的日期
@Pattern(regex=,flag=)被注解元素必須符合指定的正則表達式
@NotBlank驗證非空,且長度必須大于0
@Email被注解的元素必須是電子郵件地址
@Length(max=,min=)被注解的字符串大小必須在指定的范圍內(nèi)
@NotEmpty被注解的字符串必須非空
@Range(max=,min=)被注解的元素必須在指定范圍內(nèi)

2.校驗規(guī)則注解例子

// 空和非空檢查: @Null、@NotNull、@NotBlank、@NotEmpty
@Null(message = "驗證是否為 null")
private Integer isNull;

@NotNull(message = "驗證是否不為 null, 但無法查檢長度為0的空字符串")
private Integer id;

@NotBlank(message = "檢查字符串是不是為 null,以及去除空格后長度是否大于0")
private String name;
  
@NotEmpty(message = "檢查是否為 NULL 或者是 EMPTY")
private List<String> stringList;
  
// Boolean值檢查: @AssertTrue、@AssertFalse
@AssertTrue(message = " 驗證 Boolean參數(shù)是否為 true")
private Boolean isTrue;
  
@AssertFalse(message = "驗證 Boolean 參數(shù)是否為 false ")
private Boolean isFalse;
  
// 長度檢查: @Size、@Length
@Size(min = 1, max = 2, message = "驗證(Array,Collection,Map,String)長度是否在給定范圍內(nèi)")
private List<Integer> integerList;
 
@Length(min = 8, max = 30, message = "驗證字符串長度是否在給定范圍內(nèi)")
private String address;
 
// 日期檢查: @Future、@FutureOrPresent、@Past、@PastOrPresent
@Future(message = "驗證日期是否在當前時間之后")
private Date futureDate;
 
@FutureOrPresent(message = "驗證日期是否為當前時間或之后")
private Date futureOrPresentDate;
 
@Past(message = "驗證日期是否在當前時間之前")
private Date pastDate;
 
@PastOrPresent(message = "驗證日期是否為當前時間或之前")
private Date pastOrPresentDate;
 
// 其它檢查: @Email、@CreditCardNumber、@URL、@Pattern、
@ScriptAssert、@UniqueElements
@Email(message = "校驗是否為正確的郵箱格式")
private String email;
 
@CreditCardNumber(message = "校驗是否為正確的信用卡號")
private String creditCardNumber;
 
@URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校驗是否為正確的URL地址")
private String url;
 
@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正則校驗是否為正確的手機號")
private String phone;
  
// 對關(guān)聯(lián)對象元素進行遞歸校驗檢查
@Valid
@UniqueElements(message = "校驗集合中的元素是否唯一")
private List<CalendarEvent> calendarEvent;

@Data
@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "通過腳本表達式校驗參數(shù)")
private class CalendarEvent {
 private Date startDate;
 private Date endDate;
}

// 數(shù)值檢查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits
@Min(value = 0, message = "驗證數(shù)值是否大于等于指定值")
@Max(value = 100, message = "驗證數(shù)值是否小于等于指定值")
@Range(min = 0, max = 100, message = "驗證數(shù)值是否在指定值區(qū)間范圍內(nèi)")
private Integer score;

@DecimalMin(value = "10.01", inclusive = false, message = "驗證數(shù)值是否大于等于指定值")
@DecimalMax(value = "199.99", message = "驗證數(shù)值是否小于等于指定值")
@Digits(integer = 3, fraction = 2, message = "限制整數(shù)位最多為3,小數(shù)位最多為2")
private BigDecimal money;

關(guān)于怎么在springboot中利用hibernate validation對參數(shù)進行校驗就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI