Bean Validation(JSR-380)

Posted by Adam on August 24, 2022
Bean Validation(JSR-380)是由 Java 社群提出的一套「標準化物件(Bean)驗證」規範,定義了一組註解(Annotation)和 API,讓開發者可以以宣告式的方式(Annotation)在 Java 物件屬性、方法參數或回傳值上,定義校驗條件,並由統一的驗證引擎(如 Hibernate Validator)在執行時自動檢查是否符合這些條件。 主要特色 1. 標準化:JSR-380(也稱為 Bean Validation 2.0)是 Java EE/Jakarta EE 的一部分。 2. 宣告式:使用註解(Annotation)宣告欄位或方法參數的驗證規則。 3. 可擴充:可透過自定義 Constraint 和 ConstraintValidator 實作特殊驗證邏輯。 4. 與容器整合:支援在 Spring、Java EE、Quarkus… 等框架中,結合 @Valid 自動觸發驗證並處理錯誤。 常見註解(Constraint Annotation) • @NotNull — 值不允許為 null • @NotEmpty — 字串非空(null 或長度 0 均視為違規) • @NotBlank — 字串非空且至少包含一個非空白字元 • @Size — 限制集合、字串或陣列長度(min / max) • @Min/@Max — 數值、時間大小限制 • @Pattern — 字串需符合指定的正則表達式 • @Email — 電子郵件格式(JSR-380 新增) • @Past/@Future — 時間型別必須在過去/未來 範例—基本用法 ```java import javax.validation.constraints.*; public class User { @NotNull(message="帳號不可為空") @Size(min=4, max=20, message="帳號長度要介於4到20字元") private String username; @NotBlank(message="密碼不可為空") @Size(min=6, message="密碼至少6位") private String password; @Min(value=18, message="年齡至少18歲") private int age; @Email(message="Email 格式不正確") private String email; // getters/setters... } ``` 程式化驗證示範 ```java import javax.validation.*; import java.util.Set; ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); User user = new User(); // user.setUsername(null); // or invalid values… Set<ConstraintViolation<User>> violations = validator.validate(user); for (ConstraintViolation<User> v : violations) { System.out.println( "Property '" + v.getPropertyPath() + "' " + v.getMessage()); } ``` 和 Spring Boot 結合 在 Controller 方法參數上加 @Valid,並接收 BindingResult 或直接讓框架拋出例外: ```java @PostMapping("/users") public ResponseEntity<?> create( @Valid @RequestBody User user, BindingResult br) { if (br.hasErrors()) { // 處理驗證錯誤 } // 正常邏輯 } ``` 進階特性 • Group 驗證:可依不同情境定義多組驗證群組(Group)。 • Container Element Constraints(JSR-380 新增):可對 List<@NotNull String> 等泛型元素直接加註解。 • 自訂 Constraint:撰寫自己的註解和對應的 ConstraintValidator,實作特殊規則。 • 訊息國際化:透過 message key + Resource Bundle,提供多國語系錯誤訊息。 小結 Bean Validation(JSR-380)提供一個統一、可擴充、宣告式的驗證機制,不僅減少重複程式碼,也能與各種 Java 框架無縫整合,輕鬆地為物件或 API 請求資料加上嚴謹、可維護的驗證邏輯。 --- 在 Spring(或 Spring Boot)專案中,你可以透過 Bean Validation(JSR-380,Hibernate Validator 為常見實作)來對資料做注解式(annotation-based)驗證。其中,`@Pattern` 用來驗證一個 `String` 欄位是否符合指定的正規表達式(regex)。 以下示範一個完整的流程,包括:加入相依、定義 DTO、Controller 接收請求、驗證失敗時回傳錯誤資訊。 --- 1. 加入相依(以 Spring Boot 為例) 在 `pom.xml` 中加入: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> ``` 或 Gradle: ``` implementation 'org.springframework.boot:spring-boot-starter-validation' ``` 2. 定義 DTO(或 Entity) 假設我們要驗證使用者輸入的手機號碼 (只允許 09 開頭共 10 碼數字) 以及電子郵件: ```java import javax.validation.constraints.Pattern; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; public class UserDto { @NotBlank(message = "請填寫手機號碼") @Pattern( regexp = "^09\\d{8}$", message = "手機號碼格式錯誤,必須以09開頭共10碼數字" ) private String mobile; @NotBlank(message = "請填寫電子郵件") @Email(message = "電子郵件格式不正確") private String email; // getter / setter } ``` 重點: - `regexp`:正規表達式,不含兩側的 `/ /`。 - `message`:錯誤提示,可自訂;若需要國際化,可改為 `message = "{user.email.invalid}"`,並在 `ValidationMessages.properties` 內設定。 3. 在 Controller 中啟用驗證 ```java import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping("/api/users") public class UserController { @PostMapping public ResponseEntity<?> createUser( @Valid @RequestBody UserDto userDto, BindingResult bindingResult) { if (bindingResult.hasErrors()) { // 取出所有錯誤訊息 List<String> errors = bindingResult.getFieldErrors() .stream() .map(f -> f.getField() + ": " + f.getDefaultMessage()) .collect(Collectors.toList()); return ResponseEntity .badRequest() .body(Map.of("errors", errors)); } // 驗證通過,後續處理... return ResponseEntity.ok(Map.of("message", "建立成功")); } } ``` 重點: - `@Valid`:啟動資料驗證。 - `BindingResult`:攔截驗證結果,方便自訂回傳內容。 4. 在 Controller 類別上使用 `@Validated`(可驗證路徑參數、方法參數) 若要對 `@PathVariable`、`@RequestParam` 等做驗證,Controller 類級加上: ```java import org.springframework.validation.annotation.Validated; @RestController @RequestMapping("/api") @Validated public class MyController { @GetMapping("/items/{id}") public ResponseEntity<?> getItem( @PathVariable @Pattern(regexp="\\d+", message="ID 必須為數字") String id) { // id 通過驗證後才進來 return ResponseEntity.ok("item " + id); } } ``` 5. 自訂 ValidationMessages.properties(國際化) 在 `src/main/resources` 下新增 `ValidationMessages.properties`: ``` user.mobile.invalid=手機號碼必須以09開頭,共10位數字 user.email.invalid=請輸入有效的電子郵件地址 ``` 然後在 DTO 中: ```java @Pattern(regexp="^09\\d{8}$", message="{user.mobile.invalid}") private String mobile; ``` 6. 測試範例 使用 Postman 或 curl: ``` POST http://localhost:8080/api/users Content-Type: application/json { "mobile": "091234567", "email": "not-an-email" } ``` 可能回傳: ```json { "errors": [ "mobile: 手機號碼格式錯誤,必須以09開頭共10碼數字", "email: 電子郵件格式不正確" ] } ``` --- 常見注意事項 1. Java 字串內的 `\` 必須用 `\\`。 2. `@Pattern` 僅對 `String` 型別生效,對 `null` 不會報錯(若不允許 `null`,需搭配 `@NotNull` 或 `@NotBlank`)。 3. 若要設定全域 Validator(如自訂訊息取代),可在 `@Configuration` 中定義 `LocalValidatorFactoryBean`。 這樣,你就可以透過 `@Pattern` 在 Spring 中輕鬆地為欄位加上正規表達式驗證了!