ControllerAdvice 란?
- @ExceptionHandler 를 모아서 글로벌하게 적용할 때 쓰는 애노테이션
- 종류
- @ControllerAdvice
- @RestControllerAdvice = @ControllerAdvice + @ResponseBody
- 속성
- value == basePackages
- basePackages: 적용 범위를 문자열을 이용해 특정 패키지로 지정
- basePackageClasses: 적용 범위를 대표 클래스 한 개를 이용해 특정 패키지로 지정
- basePackages 를 type-safe 하게 사용하기 위해 제공하는 옵션
- assignableTypes: 적용 범위를 특정 클래스에 할당할 수 있는 컨트롤러로 지정
- annotations: 적용 범위를 특정 애노테이션을 사용한 컨트롤러로 지정
코드 예시
- ErrorCode, GeneralException, APIErrorResponse 을 구현한 부분은 생략한다.
@RestControllerAdvice(annotations = RestController.class)
public class ApiExceptionHandler {
@ExceptionHandler
public ResponseEntity<APIErrorResponse> general(GeneralException e) {
ErrorCode errorCode = e.getErrorCode();
HttpStatus status = errorCode.isClientSideError() ?
HttpStatus.BAD_REQUEST :
HttpStatus.INTERNAL_SERVER_ERROR;
return ResponseEntity
.status(status)
.body(APIErrorResponse.of(
false, errorCode, errorCode.getMessage(e)
));
);
}
@ExceptionHandler
public ResponseEntity<APIErrorResponse> exception(Exception e {
ErrorCode errorCode = ErrorCode.INTERNAL_ERROR;
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return ResponseEntity
.status(status)
.body(APIErrorResponse.of(
false, errorCode, errorCode.getMessage(e)
));
}
}
- 먼저 API 의 에러만을 핸들링 하기위해서 @RestControllerAdvice(annotations = RestController.class) 로 설정했다. -> 화면을 반환하는 경우는 제외된다.
- 직접 커스텀한 GeneralException 의 경우, general 에서 핸들링된다.
- 클라이언트 쪽 에러인지, 서버쪽 에러인지 체크해서 상태를 반환한다.
- 바디에는 에러 메세지를 넣어준다.
- 다음으로, 그 밖에 에러들은 exception 에서 핸들링된다.
- 서버에러로 간주하고 상태를 반환한다.
- 바디에는 에러 메세지를 넣어준다.
- 하지만 저렇게만 구현하면 Spring MVC 에서 내부적으로 발생하는 예외들을 상세하게 처리할 수 없다.
- ResponseEntityExceptionHandler 를 구현하면, 위의 문제를 해결할 수 있다.
ResponseEntityExceptionHandler
- Spring MVC 에서 내부적으로 발생하는 예외들을 처리하는 클래스다.
- 위와 같이 다양한 에러를 핸들링 해준다.
- API 예외 처리를 담당하는 @ControllerAdvice 클래스에서 상속 받아 사용한다.
- 커스터마이징을 원하는 특정 메소드를 오버라이드해서 커스텀 할 수 있다.
- 아래의 handlerExceptionInternal 메소드를 오버라이드 해서 body 에 값을 넣어줄 것이다.
- 위와 같이 현재는 body 가 null 로 세팅되어져 있다.
@RestControllerAdvice(annotations = RestController.class)
public class APIExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler
public ResponseEntity<Object> general(GeneralException e, WebRequest request) {
ErrorCode errorCode = e.getErrorCode();
HttpStatus status = errorCode.isClientSideError() ?
HttpStatus.BAD_REQUEST :
HttpStatus.INTERNAL_SERVER_ERROR;
return super.handleExceptionInternal(
e,
APIErrorResponse.of(false, errorCode.getCode(), errorCode.getMessage(e)),
HttpHeaders.EMPTY,
status,
request
);
}
@ExceptionHandler
public ResponseEntity<Object> exception(Exception e, WebRequest request) {
ErrorCode errorCode = ErrorCode.INTERNAL_ERROR;
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return super.handleExceptionInternal(
e,
APIErrorResponse.of(false, errorCode.getCode(), errorCode.getMessage(e)),
HttpHeaders.EMPTY,
status,
request
);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
ErrorCode errorCode = status.is4xxClientError() ?
ErrorCode.SPRING_BAD_REQUEST :
ErrorCode.SPRING_INTERNAL_ERROR;
return super.handleExceptionInternal(
ex,
APIErrorResponse.of(false, errorCode.getCode(), errorCode.getMessage(ex)),
headers,
status,
request
);
}
}
- APIErrorResponse.of(false, errorCode.getCode(), errorCode.getMessage(ex)) 로 바디를 세팅해 줬다.
- 보는 것과 같이 반환값을 부모의 handleExceptionInternal 다시 호출한다. -> body 를 채워주는것 말고는 부모의 로직을 그대로 따르게 구현하기 위함이다.
- general, exception 도 handleExceptionInternal 메소드로 반환함으로써, 통일성을 맞출 수 있다.
- 하지만 반환값이 ResponseEntity 가 아닌 ResponseEntity 로 변경해야 한다.
REFERENCES
- 김은호님의 SpringMVC
'Spring' 카테고리의 다른 글
함수 기반 API 설계 (0) | 2022.03.19 |
---|---|
Spring Cache Abstraction (0) | 2022.03.19 |
설정파일(Properties) 관리 (0) | 2022.03.19 |
Null Safety (0) | 2022.03.14 |
프락시 팩토리빈과 @AspectJ (0) | 2022.03.11 |