你一定有經驗在網頁的表單輸入錯誤資訊時旁邊出現紅字提示,比如說在輸入身分證字號時輸入不符合規則的身分證字號,又或者輸入了一個不可能存在的生日資訊。出現紅字代表他有做資料驗證,前端驗證可以減輕後端驗證的負擔,後端驗證則是為系統接收資料的最後一道重要關卡,今日我們就來看看Spring MVC如何處理這一塊。
(1) 請參考Day27 module
(2) 使用JSON相關設置請參考Day29
Java最早的JSR-303 Bean Validation就是來做資料驗證的規範,目前Bean Validataion的版本已到3.0,對應目前穩定版本的實作是Hibernate Validator8.0
reference:https://hibernate.org/validator/releases/
我們使用3.0的規範我們從官網知道Jakarta Bean Validation是屬於Java EE的規格而hibernate提供了實作,按照官網的提供的maven資訊引入對應的jar
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.1.Final</version>
</dependency>
透過此註解啟用驗證,就會對設定好的bean進行驗證
會將此驗證的結果封裝到BindingResult物件
Employee.java
public class Employee {
@NotNull
private String empId;
//透過message可以自訂義錯誤返回值
@NotEmpty(message = "員工姓名不為空")
private String name;
@Email
@NotNull
private String email;
@Min(0)
@Max(110)
@NotNull
private int age;
//getter and setter省略
}
可以透過BindingResult取得驗證不通過的訊息再進行封裝返回前端
@Controller
public class DemoValidateController {
@ResponseBody
@PostMapping("SaveEmp")
public String DemoValidate(@RequestBody @Validated Employee emp, BindingResult result) {
result.getFieldErrors().forEach(err->{
System.out.println(err.getField()+":"+err.getDefaultMessage());
});
return result.toString();
}
}
postman test
backend result
以下就試寫一個性別的驗證器
implements ConstraintValidator並覆寫isValid方法
public class GenderValidator implements ConstraintValidator<Gender, String> {
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return s.equals("男")||s.equals("女");
}
}
這邊可以去找一個Validation的annotation來複製改寫
@Documented
//這邊要加入自己寫的Validator
@Constraint(validatedBy = {GenderValidator.class})
//這邊定義此annotation可以放置在那些地方
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Gender {
String message() default "{性別不對喔}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Employee加入gender屬性
@Gender
private String gender;
postman test
backend result
如果在每一個Controller的每一個方法中都需要自己去處理校驗結果BindingResult是不是很累人呢,此時前一天介紹的Exception Handling來解決這個問題。當沒有使用BindingResult去接驗證結果時,驗證失敗會拋出MethodArgumentNotValidException,並且可以從該Exception取出BindingResult,是不是很棒呢
於DemoValidateController再創建一個方法
@ResponseBody
@PostMapping("SaveEmployee")
public ResponseEntity<String> DemoValidateWithExceptionHandling(@RequestBody @Validated Employee emp) {
System.out.println("Access SaveEmployee...");
return ResponseEntity.ok("Save Employee success!!!");
}
創建GlobalExceptionHandler
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> HandleException(MethodArgumentNotValidException ex){
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
}