Bean Validation 是 Java EE 和 Java SE 的一個規範,它提供了一種標準化的方法來驗證 Java Bean 的屬性
這個框架使用註解
來定義驗證規則,大大簡化了數據驗證的過程
相關的套件位置
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
大多數 Spring Boot 的 starter 都不需要明確指定版本。這是因為 Spring Boot 使用了「依賴管理」機制,所有的 starter 都會自動繼承 Spring Boot 的版本號
在這裡增加了 title 的驗證,後面會有 annotation 的相關說明
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
<!-- 這裡只列出了 validation 相關的 import -->
public class Todo {
private Long id;
@NotBlank(message = "標題不能為空")
@Size(min = 1, max = 100, message = "標題長度必須在1到100個字符之間")
private String title;
private boolean completed;
// 建構子、getter 和 setter
}
我們只先修改了 create,另外為了方便,這裡只列出和 validation 相關的程式碼
@RestController
@RequestMapping("/api/todos")
public class TodoController {
<!-- 為了方便,這裡只列出和 validation 相關的程式碼 -->
@PostMapping
public ResponseEntity<MyApiResponse<Todo>> createTodo(@Valid @RequestBody Todo todo) {
// 原本的程式碼
}
}
@RequestBody
參數前面,而不是 Todo 類別的定義上MethodArgumentNotValidException
對於大多數基本的驗證需求,只要使用 @Valid 就足夠了
另外可以用 @Validated 在更複雜的驗證場景,如分組驗證或在非控制器類中進行方法級別的驗證
發送一個沒有 title 的 request 來測試
POST http://localhost:8080/api/todos
Content-Type: application/json
{
"completed": false
}
可以看到返回了 400 Invalid request content
看一下 debug log,可以發現,我們之前的範例,使用了 GlobalExceptionHandler 繼承 ResponseEntityExceptionHandler
,所以被它的方法給捕捉到
我們可以 override 它,來返回比較詳細的資訊
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
List<String> validationErrors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
MyApiResponse.ErrorDetails error = new MyApiResponse.ErrorDetails(
"https://example.com/errors/validation-error",
"Validation Error",
HttpStatus.BAD_REQUEST,
"請求參數驗證失敗",
request.getDescription(false)
);
error.setValidationErrors(validationErrors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new MyApiResponse<>(false, null, error));
}
還要修改 ErrorDetails 加上 validationErrors
的部分
public static class ErrorDetails {
private List<String> validationErrors;
// getter 和 setter
// 其它程式碼
}
重新測試一下,就可以看到錯誤訊息比較容易理解了
@NotNull
:確保值不為 null@NotEmpty
:確保字串、集合或數組不為 null 且長度大於 0@NotBlank
:確保字串不為 null 且去除前後空白後長度大於 0@Size
:限制字串、集合或數組的大小@Min
和 @Max
:限制數值的最小值和最大值@Email
:驗證字串是否為有效的電子郵件地址格式@Past
和 @Future
:驗證日期是否在當前日期之前或之後@Positive
和 @Negative
:驗證數值是否為正數或負數@DecimalMin
和 @DecimalMax
:限制小數的最小值和最大值@AssertTrue
和 @AssertFalse
:驗證 bool 是否為 true 或 falseSpring Validator 是 Spring 框架提供的另一種驗證方式,它允許開發者建立自定義的驗證邏輯,特別適用於複雜的驗證場景
Spring Validator 的用途
注意這裡要 import 的是 springframework.validation
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
<!-- 這裡只列出了 validation 相關的 import -->
@Component
public class TodoValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Todo.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Todo todo = (Todo) target;
if (todo.getTitle() != null && todo.getTitle().contains("urgent") && !todo.isCompleted()) {
errors.rejectValue("completed", "urgent.not.completed", "Urgent todos must be completed");
}
}
}
這個類別實現了 Spring 框架中的 Validator
介面,該介面定義了兩個方法
supports(Class<?> clazz)
方法
validate(Object target, Errors errors)
方法
驗證邏輯
@RestController
@RequestMapping("/api/todos")
public class TodoController {
<!-- 為了方便,這裡只列出和 Validator 相關的程式碼 -->
private TodoValidator todoValidator;
public TodoController(TodoValidator todoValidator) {
this.todoValidator = todoValidator;
}
@PostMapping
public ResponseEntity<ApiResponse<Todo>> createTodo(@RequestBody Todo todo,
BindingResult bindingResult) {
todoValidator.validate(todo, bindingResult);
if (bindingResult.hasErrors()) {
MyApiResponse.ErrorDetails error = new MyApiResponse.ErrorDetails(
"https://example.com/errors/validation-error",
"Validation Error",
HttpStatus.BAD_REQUEST,
"There were validation errors",
"/api/todos"
);
List<String> errorMessages = bindingResult.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
error.setValidationErrors(errorMessages);
return ResponseEntity.badRequest().body(new MyApiResponse<>(false, null, error));
}
// 原本 Todo 的新增邏輯
}
}
使用注入的 TodoValidator
驗證器驗證 todo 物件
如果驗證有問題的話,Validator 會把錯誤放到 BindingResult
裡面
我們在從 BindingResult
裡面取出錯誤訊息
發送一個符合 validator 驗證邏輯的 request 來測試
POST http://localhost:8080/api/todos
Content-Type: application/json
{
"title": "學習 Spring Boot - urgent",
"completed": false
}
符合 TodoValidator 的驗證邏輯的話,就會發生錯誤
優點
缺點
優點
缺點
在實際應用中,我們可以使用 Bean Validation 來處理基本的屬性驗證,同時使用 Spring Validator 來實現更複雜的業務邏輯驗證
這種組合方法允許我們充分利用兩種驗證方式的優點,為應用提供全面而靈活的數據驗證解決方案
Bean Validation 和 Spring Validator 都是強大的資料驗證工具,各有其優缺點
Bean Validation 適用於簡單直接的屬性驗證,而 Spring Validator 則更適合複雜的業務邏輯驗證
在實際應用中,根據具體需求選擇合適的驗證方式,或者結合使用兩者,可以建立出更加健壯和可維護的應用程式
同步刊登於 Blog 「Spring Boot API 開發:從 0 到 1」Day 16 資料驗證
我的粉絲專頁
圖片來源:AI 產生