@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public ResponseEntity<ErrorResponse> handleBindException(BindException ex) {
List<ErrorResponse.FieldError> errors = ex.getBindingResult()
.getFieldErrors().stream()
.map(fe -> {
String field = fe.getField();
String code = fe.getCode(); // typeMismatch, NotBlank, Size, ...
String message = fe.getDefaultMessage(); // 메시지 소스에서 치환된 최종 메시지
// (선택) code에 따라 메시지 커스터마이징
if ("typeMismatch".equals(code)) {
message = String.format("'%s' 필드는 올바른 형식이 아닙니다.", field);
}
return new ErrorResponse.FieldError(field, code, message);
})
.collect(Collectors.toList());
ErrorResponse body = new ErrorResponse("잘못된 입력이 있습니다.", errors);
return ResponseEntity
.badRequest()
.body(body);
}
// (Optional) @RequestBody + @Valid 검증용
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleJsonValid(MethodArgumentNotValidException ex) {
// ex.getBindingResult() 활용 방식은 위와 동일
// 필요하면 여기서 따로 처리
return handleBindException(new BindException(ex.getBindingResult()));
}
// 에러 응답 DTO
public static class ErrorResponse {
private String message;
private List<FieldError> errors;
// constructors, getters
public static class FieldError {
private String field;
private String code;
private String reason;
// constructors, getters
}
}
}
다음과 같이
@ModelAttribute 에서 Validation에 실패할 경우(Controller에 BindingResult가 없는 경우 BindinException 이 발생한다.
@RequestBody에서는 Validation에 실패할 경우
MethodArgumentNotValidException 이 발생하는것을 알수있다.
위의 두경우는 BindingResult 가 없는 경우에 해당한다.(그래서 에러헨들러가 의미가 있는것)
하지만 Controller 에 BindingResult가 있는경우 중요한점이 @ModelAttribute 에서는 필드 하나하나를 검증하기 때문에
타입오류가 발생해도 Validation에서 type이 맞지않은 에러를(이름이 기억이 안남) BindingResult 에 추가를 하지만
@RequestBody 에서는 타입이 맞지않으면 애초에 객체 자체가 생성이 안되기 때문에 Validation 자체가 적용이 안된다