Spring AOP - @RestControllerAdvice를 이용한 API 예외 처리
외부망 프로젝트 개발 시에 AES암호화, MapConverter 등 여러 Util이 필요하다보니.. Controller 내에서 다양한 Exception 처리가 필요하게 되었다.
단순 Controller내에서 try-catch문으로 예외처리를 하다보니 코드 가독성도 떨어지고 코드도 너무 길어질 뿐더러, 중복된 코드 사용으로 생산성도 떨어졌다.
뿐만아니라, 기존 운영중인 서비스 중 try-catch문을 중첩 & 중복으로 사용하여 예외를 처리하고 있는 서비스를 유지보수할 때 코드 분석에 꽤나 애를 먹었던 기억이 있었기 때문에, 보다 깔끔하게 예외를 처리할 수 있는 방법을 찾아보았다.
이상적이게도 Spring에서는 AOP를 바탕으로, 기존 Controller의 핵심 로직과 예외처리 로직을 분리해 처리할 수 있도록 하는 RestControllerAdvice Annotation을 사용할 수 있었다.
@RestControllerAdvice
RestControllerAdvice는 이름에서 알 수 있듯, RestController에서 사용하며 기존 @ControllerAdvice에 ResponseBody의 기능을 더해준 것과 같다.
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
@ControllerAdvice는 그럼 무엇인가?
Class에 선언하며, 예외 처리, 바인딩 설정, 모델 객체를 모든 컨트롤러 전반에 걸쳐 적용하고 싶은 경우에 사용한다.
•
Controller와 RestController만 ExceptionHandler의 감시 대상이 된다.
•
@ControllerAdvice(com.board.api.BoardApi)와 같이 특정한 클래스만 명시하는 것도 가능하다.
◦
예외를 처리하는 @ExceptionHandler와 함께 사용하면 전반에 걸친 예외처리가 가능하다.
◦
바인딩 또는 검증을 설정할 수 있는 @InitBinder와 함께 사용하면 전반에 걸친 바인딩 설정이 가능하다.
◦
모델 정보를 초기에 초기화할 수 있는 @ModelAttribute와 함께 사용하면 전반에 걸친 모델 정보 설정이 가능하다.
즉, @RestControllerAdvice는 @ExceptionHandler와 함께 사용하면, Controller 전반에 일어나는 예외를 처리할 수 있으며, ReponseBody로 객체를 직접 return 할 수 있다.
현재 개발 중인 프로젝트에서는 RestAPI서버를 구축하고 있기 때문에, @RestControllerAdvice를 사용하여 예외를 처리 로직을 개발하였다.
@ExceptionHandler와 함께 사용하자!
•
@ControllerAdvice 이 명시된 클래스 내부 메소드 에 사용하며, Attribute로 Exception 클래스를 받는다.
◦
RuntimeException.class 나 더 상위 클래스인 Exception.class 등을 넘기면 된다.
•
Custom Exception을 만들었다면 (보통은 RuntimeException을 상속 받았을 것이다.) 이를 넘기면 된다.
•
@ExceptionHandler(XXException.class) 라고 작성한 경우, @ControllerAdvice에서 명시한 클래스에서 throw new XXException( .. ) 이 발생하면 핸들러는 이를 감지하고 해당 메소드를 수행한다.
•
메소드는 여러개 작성 할 수 있으며 이에 따라 @ExceptionHandler 에 다른 Attribute값을 넘김으로써 각 Exception을 다르게 처리 할 수 있다.
구조
•
Controller내에서 일어나는 예외를 잡아 처리할 Class를 선언하고, 예외 처리할 범위를 설정한다. (Package 단위)
•
위의 예외를 받아 처리할 Handler를 구현한다.
◦
예외 별로 처리가 가능하도록 분기한다.
◦
에러에 대한 응답 값을 Client에 내려준다. (공통 응답)
@RestControllerAdvice
public class ExceptionAdvice {
/**
* Interface 서버와 일시적 통신 실패 에러
*
* @Param e
* @Return ResponseEntity<BaseResponse>
*/
@ExceptionHandler(TempConnectFailException.class)
public ResponseEntity<BaseResponse> temporaryConnectionFailHandler(TempConnectFailException e) {
BaseResponse baseResponse = BaseResponse.of(ErrorCode.TEMPORARY_CONNECT_ERROR);
log.error("### temporaryConnectionFailHandler : " + e.getMessage());
baseResponse.setMessage(e.getMessage() + baseResponse.getMessage());
return new ResponseEntity<>(baseResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Java
복사
참고)