Рассмотрим пример ниже, где я реализовал что-то вроде того, что вы просите:
@RestController
@RequestMapping("/accounts")
public class SavingsAccountController {
private final BankAccountService accountService;
@Autowired
public SavingsAccountController(SavingsAccountService accountService) {
this.accountService = accountService;
}
@PutMapping("withdraw")
public ResponseEntity<AccountBalance> onMoneyWithdrawal(@RequestBody @Validated WithdrawMoney withdrawal, BindingResult errors) {
//this is the validation barrier
if (errors.hasErrors()) {
throw new ValidationException(errors);
}
double balance = accountService.withdrawMoney(withdrawal);
return ResponseEntity.ok(new AccountBalance(
withdrawal.getAccountNumber(), balance));
}
@PutMapping("save")
public ResponseEntity<AccountBalance> onMoneySaving(@RequestBody @Validated SaveMoney savings, BindingResult errors) {
//this is the validation barrier
if (errors.hasErrors()) {
throw new ValidationException(errors);
}
double balance = accountService.saveMoney(savings);
return ResponseEntity.ok(new AccountBalance(
savings.getAccountNumber(), balance));
}
}
В приведенном выше коде мы используем Bean Validation, чтобы проверить, что пользовательский DTO содержит достоверную информацию. Любые ошибки, обнаруженные в DTO, предоставляются через переменную BindingResult
errors, из которой разработчик может извлечь все подробности того, что пошло не так на этапе проверки.
Чтобы разработчикам было легче с ними справиться. В этом шаблоне в приведенном выше коде я просто оборачиваю BindingResult
в пользовательский ValidationException
, который знает, как извлекать сведения об ошибке проверки.
public class ValidationException extends RuntimeException {
private final BindingResult errors;
public ValidationException(BindingResult errors) {
this.errors = errors;
}
public List<String> getMessages() {
return getValidationMessage(this.errors);
}
@Override
public String getMessage() {
return this.getMessages().toString();
}
//demonstrate how to extract a message from the binging result
private static List<String> getValidationMessage(BindingResult bindingResult) {
return bindingResult.getAllErrors()
.stream()
.map(ValidationException::getValidationMessage)
.collect(Collectors.toList());
}
private static String getValidationMessage(ObjectError error) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
String className = fieldError.getObjectName();
String property = fieldError.getField();
Object invalidValue = fieldError.getRejectedValue();
String message = fieldError.getDefaultMessage();
return String.format("%s.%s %s, but it was %s", className, property, message, invalidValue);
}
return String.format("%s: %s", error.getObjectName(), error.getDefaultMessage());
}
}
Обратите внимание, что в моем определении контроллера я не использую Аннотации Bean Validation @ Valid , но аналог Spring @ Validated , но под капотом Spring будет использовать Bean Validation.
Как сериализовать настраиваемое исключение ?
В приведенном выше коде ValidationException
будет выброшено, если полезная нагрузка недействительна. Как контроллер должен создать ответ для клиента?
Есть несколько способов справиться с этим, но, возможно, самым простым решением является определение класса, помеченного как @ControllerAdvice
. В этом аннотированном классе мы разместим наши обработчики исключений для любого указанного c исключения, которое мы хотим обработать, и превратим их в допустимый объект ответа, чтобы вернуться к нашим клиентам:
@ControllerAdvice
public class ExceptionHandlers {
@ExceptionHandler
public ResponseEntity<ErrorModel> handle(ValidationException ex) {
return ResponseEntity.badRequest()
.body(new ErrorModel(ex.getMessages()));
}
//...
}
Я написал несколько другие примеры этого и других методов проверки с помощью Spring на случай, если вам может быть интересно узнать больше об этом .