2017-12-29
spring 實作的 restful api,要針對輸入參數進行內容格式驗証,可分成下列儿類
實作程式碼重點如下
@RestController
@RequestMapping("/validateAnnotationDemo")
public class ValidateAnnotationDemoController {
@RequestMapping(value = "/validateRequestBody", produces = MediaType.APPLICATION_JSON_VALUE)
public User validateRequestBody(@Valid @RequestBody User user){
...etc;
}
private static class User{
@NotBlank
private String name;
...etc;
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
...etc;
}
}
實作程式碼重點如下
@Configuration
@ComponentScan
public class WebConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
@Validated
@RestController
@RequestMapping("/validateAnnotationDemo")
public class ValidateAnnotationDemoController {
@RequestMapping(value = "/validateRequestParam", produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String,Object> validateRequestParam(@NotBlank(message = "message must not be blank") @RequestParam String message){
...etc;
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity handleConstraintViolationException(HttpServletRequest req, ConstraintViolationException e){
...etc;
}
}
@Configuration
@ComponentScan
public class WebConfig {
/**
* 注冊 MethodValidationPostProcessor bean 之後,Controller class 開頭加的 @Validated 與 method 裡針
* 對 @RequestParam annotation 加的 validate annotation (e.g. @NotBlank, @NotNull ...etc)才會生效,
* 沒注冊時就算程式碼裡有加這些 annotation 還是不會有作用
*
* @return
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
@Validated
@RestController
@RequestMapping("/validateAnnotationDemo")
public class ValidateAnnotationDemoController {
//在 class 開頭加了 @Validated 之後,針對 @RequestParam 加的 validation annotation (e.g. @NotBlank, @NotNull...etc) 才會生效
@RequestMapping(value = "/validateRequestParam", produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String,Object> validateRequestParam(
@NotBlank(message = "message must not be blank") @RequestParam String message){
Map<String,Object> result = new HashMap<>();
result.put("message", message);
return result;
}
// 加對 @Valid 之後,會驗証 pojo (此例中指 User instance) 內容格式是否正確
@RequestMapping(value = "/validateRequestBody", produces = MediaType.APPLICATION_JSON_VALUE)
public User validateRequestBody(@Valid @RequestBody User user){
return user;
}
private static class User{
@NotBlank
private String name;
// 針對 list of pojo 的資料格式進行驗証,要加上 @NotEmpty, @Valid 二個 annotation
@NotEmpty
@Valid
private List<ContactInfo> contactInfoList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<ContactInfo> getContactInfoList() {
return contactInfoList;
}
public void setContactInfoList(List<ContactInfo> contactInfoList) {
this.contactInfoList = contactInfoList;
}
}
private static class ContactInfo{
@NotBlank
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}
/**
* Controller 發生 uncatch exception 情況時,會統一在這個 class 被處理。
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity handleException(HttpServletRequest req, Exception e){
logger.error(e.getMessage(), e);
String errorMsg = (e.getMessage() == null) ? e.getClass().getSimpleName() : e.getMessage();
Map<String,Object> error = Collections.singletonMap("error", errorMsg);
return ResponseEntity.status(500).body(error);
}
/**
* Controller 裡標注 @RequestParam 的變數在 validate fail 時會丟出 ConstraintViolationException。這個 method
* 專門處理此類 exception
*
* @param req
* @param e
*
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity handleConstraintViolationException(HttpServletRequest req, ConstraintViolationException e){
logger.error(e.getMessage(), e);
// "@NotBlank @RequestParam String myArg" 這樣的 validate 寫法在 validate fail 時無法得知 "哪個輸入參數名稱" 驗証失敗,這是 java reflection 本身的限制。
// 用這類語法時要改寫成 "@NotBlank(myArg must not be blank) @RequestParam String myArg",程式裡的 validate annotation 要寫出 "完整出錯明細",
// 不然在處理 ConstraintViolationException 時只會知道驗証失敗的原因,卻不知道是哪個輸入參數名稱驗証失敗。
List<String> errorMessages = e.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
Map<String,Object> error = Collections.singletonMap("error", errorMessages);
return ResponseEntity.status(400).body(error);
}
/**
* Controller 裡標注 @RequestBody 的變數在 validate fail 時會丟出 MethodArgumentNotValidException。這個 method
* 專門處理此類 exception
*
* @param req
* @param e
*
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
logger.error(e.getMessage(), e);
List<String> errorMessages = e.getBindingResult().getFieldErrors()
.stream()
.map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage()) // 記錄 "fieldName + validateFailMessage"
.collect(Collectors.toList());
Map<String,Object> error = Collections.singletonMap("error", errorMessages);
return ResponseEntity.status(400).body(error);
}
}