Search
〰️

RestControllerAdvice (ExceptionHandler)

Spring AOP - @RestControllerAdvice를 이용한 API 예외 처리

외부망 프로젝트 개발 시에 AES암호화, MapConverter 등 여러 Util이 필요하다보니.. Controller 내에서 다양한 Exception 처리가 필요하게 되었다.
단순 Controller내에서 try-catch문으로 예외처리를 하다보니 코드 가독성도 떨어지고 코드도 너무 길어질 뿐더러, 중복된 코드 사용으로 생산성도 떨어졌다.
뿐만아니라, 기존 운영중인 서비스 중 try-catch문을 중첩 & 중복으로 사용하여 예외를 처리하고 있는 서비스를 유지보수할 때 코드 분석에 꽤나 애를 먹었던 기억이 있었기 때문에, 보다 깔끔하게 예외를 처리할 수 있는 방법을 찾아보았다.
이상적이게도 Spring에서는 AOP를 바탕으로, 기존 Controller의 핵심 로직과 예외처리 로직을 분리해 처리할 수 있도록 하는 RestControllerAdvice Annotation을 사용할 수 있었다.

@RestControllerAdvice

RestControllerAdvice는 이름에서 알 수 있듯, RestController에서 사용하며 기존 @ControllerAdviceResponseBody의 기능을 더해준 것과 같다.
@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
복사
참고)