컨트롤러에 대해 @ExceptionHandler
, @InitBinder
, @ModelAttribute
가 적용된 메소드에 AOP를 적용되기 위해 고안되었다. 여러 컨트롤러에 전역적으로 ExceptionHandler
를 적용해준다.
여기서 @ExceptionHandler
는 AOP를 이용한 예외처리 방법으로 메서드에 선언하여 예외처리를 하려는 클래스를 지정하면 예외 발생시 정의된 로직에 의해 처리한다.
@ControllerAdvice
와 @ResponseBody
를 합쳐놓은 어노테이션이다. @ControllerAdvice
와 같은 역할을 하며 @ResponseBody
를 통해 객체를 리턴할 수 있다.
@ControllerAdvice
와 @RestControllerAdvice
를 사용할 때 주의점은 한 프로젝트당 하나만 선언해야 한다는 것이다.
public enum ExceptionCode {
COMPANY_NOT_FOUND(404, "Company Not Found"),
COMPANY_ID_NOT_MATCHED(404, "Company id not matched"),
.
.
.
INTERNAL_SERVER_ERROR(500, "Method Not Allowed");
@Getter
private final int status;
@Getter
private final String message;
ExceptionCode(int status, String message) {
this.status = status;
this.message = message;
}
}
public class BusinessLogicException extends RuntimeException{
@Getter
private ExceptionCode exceptionCode;
public BusinessLogicException(ExceptionCode exceptionCode) {
super(exceptionCode.getMessage());
this.exceptionCode = exceptionCode;
}
}
이런 식으로 예외 사용이 가능하다.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) { //@valid 예외 처리
return ErrorResponse.of(e.getBindingResult());
}
@ExceptionHandler //CustomException 처리
public ResponseEntity handleBusinessLogicException(BusinessLogicException e) {
final ErrorResponse response = ErrorResponse.of(e.getExceptionCode());
return new ResponseEntity<>(response, HttpStatus.valueOf(e.getExceptionCode()
.getStatus()));
}
@ExceptionHandler
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public ErrorResponse handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
return ErrorResponse.of(HttpStatus.METHOD_NOT_ALLOWED);
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleHttpMessageNotReadableException(
HttpMessageNotReadableException e) { //잘못된 형식으로 request 를 요청할 경우 예외 발생
return ErrorResponse.of(400,
"Required request body is missing");
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingServletRequestParameterException(
MissingServletRequestParameterException e) { //필수 파라미터 결여시
return ErrorResponse.of(400,
e.getMessage());
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleConstraintViolationException( //@Validated 에러
ConstraintViolationException e) {
return ErrorResponse.of(e.getConstraintViolations());
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) {
return ErrorResponse.of(400, e.getMessage());
}
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleException(Exception e) {
log.error("# handle Exception", e);
return ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Getter
public class ErrorResponse {
private Integer status;
private String message;
private List<FieldError> fieldErrors;
private List<ConstraintViolationError> violationErrors;
public ErrorResponse(Integer status, String message) {
this.status = status;
this.message = message;
}
private ErrorResponse(final List<FieldError> fieldErrors,
final List<ConstraintViolationError> violationErrors) {
this.fieldErrors = fieldErrors;
this.violationErrors = violationErrors;
}
public static ErrorResponse of(ExceptionCode exceptionCode) {
return new ErrorResponse(exceptionCode.getStatus(), exceptionCode.getMessage());
}
public static ErrorResponse of(BindingResult bindingResult) {
return new ErrorResponse(FieldError.of(bindingResult), null);
}
public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
return new ErrorResponse(null, ConstraintViolationError.of(violations));
}
public static ErrorResponse of(HttpStatus http) {
return new ErrorResponse(http.value(), http.getReasonPhrase());
}
public static ErrorResponse of(Integer status, String message) {
return new ErrorResponse(status, message);
}
@Getter //@Validated 에러 발생시
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;
}
public 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() == null ?
"" : error.getRejectedValue().toString(),
error.getDefaultMessage()))
.collect(Collectors.toList());
}
}
@Getter // @Valid 에서 에러 발생시
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());
}
}
}