SpringBoot使用Validation包進(jìn)行輸入?yún)?shù)校驗(yàn)
Spring Boot 自帶的 spring-boot-starter-validation 包支持以標(biāo)準(zhǔn)注解的方式進(jìn)行輸入?yún)?shù)校驗(yàn)。spring-boot-starter-validation 包主要引用了 hibernate-validator 包,其參數(shù)校驗(yàn)功能就是 hibernate-validator 包所提供的。
本文即關(guān)注 spring-boot-starter-validation 包所涵蓋的標(biāo)準(zhǔn)注解的使用、校驗(yàn)異常的捕獲與展示、分組校驗(yàn)功能的使用,以及自定義校驗(yàn)器的使用。
本文示例工程使用 Maven 管理。
下面列出寫作本文時(shí)所使用的 JDK、Maven 與 Spring Boot 的版本:
JDK:Amazon Corretto 17.0.8 Maven:3.9.2 Spring Boot:3.2.1
本文以開發(fā)一個 User 的 RESTful API 為例來演示 Validation 包的使用。
所以 pom.xml 文件除了需要引入 spring-boot-starter-validation 依賴外:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
還需要引入 spring-boot-starter-web 依賴:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
為了省去 Model 類 Getters 與 Setters 的編寫,本文還使用了 lombok 依賴:
<!-- pom.xml -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
依賴準(zhǔn)備好后,即可以嘗試對 Validation 包進(jìn)行使用了。
1 Validation 標(biāo)準(zhǔn)注解的使用
下面列出 spring-boot-starter-validation 包中常用的幾個注解。
| 注解 | 作用字段類型 | 說明 |
|---|---|---|
@Null | 任意類型 | 驗(yàn)證元素值為 null |
@NotNull | 任意類型 | 驗(yàn)證元素值不為 null,無法驗(yàn)證空字符串 |
@NotBlank | CharSequence 子類型 | 驗(yàn)證元素值不為空(不為 null 且不為空字符串) |
@NotEmpty | CharSequence 子類型、Collection、Map、數(shù)組 | 驗(yàn)證元素值不為 null 且不為空(字符串長度、集合大小不為 0) |
@Min | 任何 Number 類型 | 驗(yàn)證元素值大于等于 @Min 指定的值 |
@Max | 任何 Number 類型 | 驗(yàn)證元素值小于等于 @Max 指定的值 |
@Digits | 任何 Number 類型 | 驗(yàn)證元素值的整數(shù)位數(shù)和小數(shù)位數(shù)上限 |
@Size | 字符串、Collection、Map、數(shù)組等 | 驗(yàn)證元素值的在指定區(qū)間之內(nèi),如字符長度、集合大小 |
@Range | 數(shù)值類型 | 驗(yàn)證元素值在最小值和最大值之間 |
@Email | CharSequence 子類型 | 驗(yàn)證元素值是電子郵件格式 |
@Pattern | CharSequence 子類型 | 驗(yàn)證元素值與指定的正則表達(dá)式匹配 |
@Valid | 任何非原子類型 | 指定遞歸驗(yàn)證關(guān)聯(lián)的對象 |
下面就看一下如何使用這些注解。
假設(shè)我們想編寫一個創(chuàng)建 User 的 RESTful API,而創(chuàng)建 User 時(shí),其中有一些字段是有校驗(yàn)規(guī)則的(如:必填、滿足字符串長度要求、滿足電子郵件格式、滿足正則表達(dá)式等)。
下面即看一下使用了 Validation 注解的 User Model 代碼:
// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class User {
@NotNull(message = "name can not be null")
@Size(min = 2, max = 20, message = "name length should be in the range [2, 20]")
private String name;
@NotNull(message = "age can not be null")
@Range(min = 18, max = 100, message = "age should be in the range [18, 100]")
private Integer age;
@NotNull(message = "email can not be null")
@Email(message = "email invalid")
private String email;
@NotNull(message = "phone can not be null")
@Pattern(regexp = "^1[3-9][0-9]{9}$", message = "phone number invalid")
private String phone;
}
下面淺析一下 User Model 中每個字段的校驗(yàn)規(guī)則:
name
其為字符串類型,使用了
@NotNull、@Size注解,表示這個字段為必填,且字符串長度應(yīng)屬于區(qū)間[2, 20]。age
其為整數(shù)類型,使用了
@NotNull、@Range注解,表示這個字段為必填,且數(shù)值應(yīng)屬于區(qū)間[2, 20]。email
其為字符串類型,使用了
@NotNull、@Email注解,表示這個字段為必填,且為 Email 格式。phone
其為字符串類型,使用了
@NotNull、@Pattern注解,表示這個字段為必填,且為合法的國內(nèi)手機(jī)號格式。
下面看一下統(tǒng)一的錯誤返回 Model 類 ErrorMessage 的代碼:
// src/main/java/com/example/demo/model/ErrorMessage.java
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ErrorMessage {
private String code;
private String description;
}
最后看一下 UserController 的代碼:
// src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;
import com.example.demo.model.ErrorMessage;
import com.example.demo.model.User;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("")
public ResponseEntity<?> addUser(@RequestBody @Valid User user, BindingResult result) {
if (result.hasErrors()) {
List<ObjectError> allErrors = result.getAllErrors();
if (!allErrors.isEmpty()) {
ObjectError error = allErrors.get(0);
String description = error.getDefaultMessage();
return ResponseEntity.badRequest().body(new ErrorMessage("validation_failed", description));
}
}
// userService.addUser(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
可以看到,UserController 的 addUser 方法使用了 User Model 來接收請求體,User Model 前使用了 @Valid 注解,該注解會對 User Model 中的字段根據(jù)注解設(shè)定的規(guī)則自動進(jìn)行校驗(yàn)。此外,addUser 方法還有另外一個參數(shù) BindingResult,該參數(shù)會捕獲所有的字段校驗(yàn)錯誤信息,本文僅是將其中的第一個錯誤按照 ErrorMessage 格式返回了出來,沒有任何錯誤信息則會返回 201 狀態(tài)碼。
下面使用 CURL 命令測試一下這個接口:
curl -L \
-X POST \
-H "Content-Type: application/json" \
http://localhost:8080/users \
-d '{"name": "Larry", "age": 18, "email": "larry@qq.com"}'
// 400
{ "code": "validation_failed", "description": "phone can not be null" }
可以看到,如果有字段不滿足校驗(yàn)規(guī)則時(shí),會返回設(shè)定的錯誤信息。
如果 Model 類中有嵌套對象,該怎么做驗(yàn)證呢?只需要在對應(yīng)的字段上加上 @Valid 注解就可以了。
比如,User Model 中有一個字段為 address,其為 Address 對象,Address 類的代碼如下:
// src/main/java/com/example/demo/model/Address.java
package com.example.demo.model;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class Address {
@NotNull(message = "province can not be null")
@Size(min = 2, max = 100, message = "province length should be in the range [10, 100]")
private String province;
@NotNull(message = "city can not be null")
@Size(min = 2, max = 100, message = "city length should be in the range [10, 100]")
private String city;
@NotNull(message = "street can not be null")
@Size(min = 10, max = 1000, message = "street length should be in the range [10, 1000]")
private String street;
}
則 User Model 中,若想對 address 字段應(yīng)用校驗(yàn)規(guī)則,則需要額外在該字段上加一個 @Valid 注解:
// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class User {
...
@Valid
@NotNull(message = "address can not be null")
private Address address;
}
了解了 Validation 包中常用注解的使用方式,下面看一下校驗(yàn)錯誤的異常捕獲與展示。
2 校驗(yàn)錯誤的異常捕獲與展示
我們注意到,上面的例子中 UserController 的 addUser 方法使用一個額外的參數(shù) BindingResult 來接收校驗(yàn)錯誤信息,然后根據(jù)需要展示給調(diào)用者。但這種處理方式有點(diǎn)太冗余了,每個請求方法都需要加這么一個參數(shù)并重新寫一遍錯誤返回的邏輯。
其實(shí)不加這個參數(shù)的話,若有校驗(yàn)錯誤,Spring Boot 框架會拋出一個 MethodArgumentNotValidException。所以簡單一點(diǎn)的處理方式是:使用 @RestControllerAdvice 注解來將一個類標(biāo)記為全局的異常處理類,針對 MethodArgumentNotValidException,只需要在這個異常處理類中進(jìn)行統(tǒng)一捕獲、統(tǒng)一處理就可以了。
異常處理類 MyExceptionHandler 的代碼如下:
// src/main/java/com/example/demo/exception/MyExceptionHandler.java
package com.example.demo.exception;
import com.example.demo.model.ErrorMessage;
import org.springframework.http.HttpStatus;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
@RestControllerAdvice
public class MyExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorMessage handleValidationExceptions(
MethodArgumentNotValidException ex) {
List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
if (!allErrors.isEmpty()) {
ObjectError error = allErrors.get(0);
String description = error.getDefaultMessage();
return new ErrorMessage("validation_failed", description);
}
return new ErrorMessage("validation_failed", "validation failed");
}
}
有了該異常處理類后,UserController 的代碼即可以變得很純凈:
// src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;
...
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("")
public ResponseEntity<?> addUser(@RequestBody @Valid User user) {
// userService.addUser(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
使用該種方式后,對于調(diào)用方來說,有校驗(yàn)錯誤時(shí),效果與之前是一樣的:
# 使用 CURL 命令新建一個 User(未提供 phone 參數(shù))
curl -L \
-X POST \
-H "Content-Type: application/json" \
http://localhost:8080/users \
-d '{"name": "Larry", "age": 18, "email": "larry@qq.com"}'
// 會返回 400 狀態(tài)碼,以及如下錯誤信息
{ "code": "validation_failed", "description": "phone can not be null" }
學(xué)會如何以統(tǒng)一的異常處理類來處理校驗(yàn)錯誤后,下面看一下如何使用分組校驗(yàn)功能。
3 分組校驗(yàn)功能的使用
分組校驗(yàn)功能可以針對同一個 Model,為不同的場景應(yīng)用不同的校驗(yàn)規(guī)則。
下面我們嘗試使用同一個 User Model 來同時(shí)接收新增和更新的請求數(shù)據(jù),但為各個字段指定不同的分組來區(qū)別新增和更新時(shí)校驗(yàn)規(guī)則的不同。
User Model 的代碼如下:
// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.validation.groups.Default;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
@Data
public class User {
@NotNull(message = "id can not be null", groups = Update.class)
private Long id;
@NotNull(message = "name can not be null", groups = Add.class)
@Size(min = 2, max = 20, message = "name length should be in the range [2, 20]")
private String name;
@NotNull(message = "age can not be null", groups = Add.class)
@Range(min = 18, max = 100, message = "age should be in the range [18, 100]")
private Integer age;
@NotNull(message = "email can not be null", groups = Add.class)
@Email(message = "email invalid")
private String email;
@NotNull(message = "phone can not be null", groups = Add.class)
@Pattern(regexp = "^1[3-9][0-9]{9}$", message = "phone number invalid")
private String phone;
public interface Add extends Default {
}
public interface Update extends Default {
}
}
可以看到,我們在 User Model 中定義了兩個分組:Add 與 Update。每個字段上都有一個 @NotNull 注解,但 id 字段的分組是 Update.class,其它字段的分組是 Add.class,其余注解則未指定分組(表示均適用)。意思是要求:在新增時(shí),name、age、email、phone 為必填字段;在更新時(shí),id 為必填字段;而且不論新增還是更新,只要提供了對應(yīng)的字段,就需要滿足對應(yīng)字段的校驗(yàn)規(guī)則。
下面看一下 UserController 的代碼:
// src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;
...
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("")
public ResponseEntity<?> addUser(@RequestBody @Validated(User.Add.class) User user) {
// userService.addUser(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@PatchMapping("")
public ResponseEntity<?> updateUser(@RequestBody @Validated(User.Update.class) User user) {
// userService.updateUser(user);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}
可以看到,新增 User 接口與更新 User 接口使用了同一個 User Model;但新增使用的分組是 User.Add.class,更新使用的分組是 User.Update.class。
注意:這里指定分組時(shí)用到了 @Validated 注解,而前面用到的是 @Valid 注解,這里簡單解釋一下兩者的不同。@Validated 注解是 Spring 框架自帶的,而 @Valid 注解是 jakarta.validation 包下的,@Validated 注解可以指定分組,而 @Valid 注解則沒有這個功能。
下面嘗試在不提供 id 字段的情況下更新一下 User:
curl -L \
-X PATCH \
-H "Content-Type: application/json" \
http://localhost:8080/users \
-d '{"name": "Larry", "age": 18, "email": "larry@qq.com"}'
會返回如下錯誤:
// 400
{ "code": "validation_failed", "description": "id can not be null" }
介紹完分組校驗(yàn)功能的使用,下面看一下自定義校驗(yàn)器的使用。
4 自定義校驗(yàn)器的使用
如果 Validation 包中自帶的注解未能滿足您的校驗(yàn)需求,則可以自定義一個注解并實(shí)現(xiàn)對應(yīng)的校驗(yàn)邏輯。
下面自定義了一個注解 CustomValidation,其代碼如下:
package com.example.demo.validation;
...
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CustomValidator.class)
public @interface CustomValidation {
String message() default "Invalid value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
如上代碼中,@Target 指定了該注解的作用域,本例中,表示該注解可應(yīng)用在方法或字段上;@Retention 指定該注解的存活期限,本例中,表示該注解在運(yùn)行時(shí)可以使用;@Constraint 指定該注解的處理類。
處理類 CustomValidator 用于編寫自定義校驗(yàn)邏輯,其代碼如下:
package com.example.demo.validation;
...
public class CustomValidator
implements ConstraintValidator<CustomValidation, String> {
@Override
public void initialize(CustomValidation constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return null != value && value.startsWith("ABC");
}
}
可以看到,CustomValidator 實(shí)現(xiàn)了 ConstraintValidator<CustomValidation, String>,表示被標(biāo)記字段是一個 String 類型;initialize() 方法用于校驗(yàn)器的初始化,可以根據(jù)需要訪問注解上的各種屬性;isValid() 方法可以拿到被校驗(yàn)的字段值,用于編寫真正的校驗(yàn)邏輯。
下面即在 User Model 中使用一下這個自定義注解:
package com.example.demo.model;
...
@Data
public class User {
@CustomValidation(message = "testField invalid")
private String testField;
}
這樣,當(dāng)這個字段值不滿足自定義校驗(yàn)規(guī)則時(shí),就會拋出對應(yīng)的錯誤:
// 400
{ "code": "validation_failed", "description": "testField invalid" }
綜上,本文以示例代碼的方式詳細(xì)介紹了 spring-boot-starter-validation 包的使用。
到此這篇關(guān)于SpringBoot使用Validation包進(jìn)行輸入?yún)?shù)校驗(yàn)的文章就介紹到這了,更多相關(guān)SpringBoot Validation參數(shù)校驗(yàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- spring?參數(shù)校驗(yàn)Validation示例詳解
- SpringBoot使用validation進(jìn)行自參數(shù)校驗(yàn)的方法
- Spring?Boot集成validation實(shí)現(xiàn)參數(shù)校驗(yàn)功能
- springboot之Validation參數(shù)校驗(yàn)詳細(xì)解讀
- SpringBoot使用Validation進(jìn)行參數(shù)校驗(yàn)的示例詳解
- SpringBoot集成Validation參數(shù)校驗(yàn)
- 基于Spring Validation實(shí)現(xiàn)全局參數(shù)校驗(yàn)異常處理的示例詳解
相關(guān)文章
idea 打包的jar運(yùn)行報(bào) "XXX中沒有主清單屬性"
這篇文章主要介紹了idea 打包的jar運(yùn)行報(bào) "XXX中沒有主清單屬性",文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
spring security中的csrf防御原理(跨域請求偽造)
這篇文章主要介紹了spring security中的csrf防御機(jī)制原理解析(跨域請求偽造),本文通過實(shí)例代碼詳解的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
Spring?Boot?+?Spring?Batch?實(shí)現(xiàn)批處理任務(wù)的詳細(xì)教程
這篇文章主要介紹了Spring?Boot+Spring?Batch實(shí)現(xiàn)批處理任務(wù),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的詳細(xì)代碼
在項(xiàng)目中開發(fā)定時(shí)任務(wù)應(yīng)該一種比較常見的需求,今天通過示例代碼給大家講解SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)知識,感興趣的朋友一起看看吧2024-06-06
java使用BeanUtils.copyProperties方法對象復(fù)制同名字段類型不同賦值為空問題解決方案
這篇文章主要給大家介紹了關(guān)于java使用BeanUtils.copyProperties方法對象復(fù)制同名字段類型不同賦值為空問題的解決方案,文中通過代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11
java 實(shí)現(xiàn) stack詳解及實(shí)例代碼
這篇文章主要介紹了java 實(shí)現(xiàn) stack詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09

