@RestControllerAdvice
애너테이션을 추가한 클래스를 이용하면 예외 처리를 공통화할 수 있다.
@RestControllerAdvice
애너테이션을 추가하면 여러개의 Controller 클래스에서 @ExceptionHandler
, @InitBinder
또는 @ModelAttribute
가 추가된 메서드를 공유해서 사용할 수 있다.@ExceptionHandler
가 추가된 메서드는 특정 클래스에서 공통으로 처리되므로 모두 제거한다.@RestControllerAdvice
public class GlobalExceptionAdvice {
}
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<ErrorResponse.FieltError> errors =
fieldErrors.stream()
.map(error -> new ErrorResponse.FieldError(
error.getField(),
error.getRejectedValue(),
error.getDefaultMessage()))
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler
public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
(1) DTO 멤버 변수 필드의 유효성 검증 실패로 발생한 에러 정보를 담는 멤버 변수
(2) URI 변수 값의 유효성 검증에 실패로 발생한 에러 정보를 담는 멤버 변수
(3) ErrorResponse 클래스의 생성자
(4) MethodArgumentNtoValidException 에 대한 ErrorResponse 객체를 생성
(5) ConstraintViolationException 에 대한 ErrorResponse 객체를 생성
(6) 필드(DTO 클래스의 멤버 변수)의 유효성 검증에서 발생하는 에러 정보를 생성
(7)URI 변수 값에 대한 에러 정보를 생성
@Getter
public class ErrorResponse {
private List<FieldError> fieldErrors; // (1)
private List<ConstraintViolationError> violationErrors; // (2)
// (3)
private ErrorResponse(final List<FieldError> fieldErrors,
final List<ConstraintViolationError> violationErrors) {
this.fieldErrors = fieldErros;
this.violationErrors = violationErrors;
}
// (4) BindinResult에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(BindingResult bindingResult) {
return new ErrorResponse(FieldError.of(bindingResult), null);
}
// (5) Set<ConstraintViolation<?>> 객체에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(Set<ConstraintViolation<?>> violation) {
return new ErrorResponse(null, ConstraintViolationError.of(violation));
}
// (6) Field Error 가공
@Getter
public static class FieldError {
private String field;
private Object rejectedValue;
private String reason;
private FieldError(String field, Object rejectedValue, String reason) {
this.field = field;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
private static List<FieldError> of(BindingResult bindingResult) {
final List<org.springframework.validation.FieldError> fieldErrors =
bindingResult.getFieldErrors();
return fieldErrors.stream()
.map(error -> new FieldError(
error.getField(),
error.getRejectedValue() == ? "" : error.getRejectedValue().toString(),
error.getDefaultMessage()))
.collect(Collectors.toList());
}
}
// (7) ConstraintViolation Error 가공
@Getter
public static class ConstraintViolationError {
private String propertyPath;
private Object rejectedValue;
private String reason;
private ConstraintViolationError(String propertyPath,
Object rejectedValue,
String reason) {
this.propertyPath = propertyPath;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<ConstraintViolationError> of(
Set<ConstraintViolation<?>> constraintViolations) {
return constraintViolations.stream()
.map(constraintViolation -> new ConstraintViolationError(
constraintViolation.getPropertyPath().toString(),
constraintViolation.getInvalidValue().toString(),
constraintViolation.getMessage()))
.collect(Collectors.toList());
}
}
}
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
@ResponseStTUS(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
final ErrorResponse response = ErrorResponse.of(e.getBindingResult());
return response;
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQEUST)
public ErrorResponse handleConstraintViolationException(
ConstraintViolationException e) {
final ErrorResponse response = ErrorResponse.of(e.getConstraintViolations());
return response;
}
}
@RestControllerAdvice
= @ControllerAdvice
+ @ResponseBody
Spring MVC 4.3 버전 이후부터 @RestControllerAdvice 애너테이션을 지원한다.
@RestControllerAdvice
애너테이션은 @ControllerAdvice
과 @ResponseBody
의 기능을 포함하고 있다.
따라서 JSON 형식의 데이터를 Response Body로 전송하기 위해서는 ResponseEntity로 데이터를 래핑할 필요가 없다.