목표
전역 예외처리를 위한 @ControllerAdvice와 @RestControllerAdvice에 대해서 알아보겠습니다.
개요
진행하던 프로젝트에서 리팩토링이 시급한 부분은 전체적인 구조였습니다. 복잡하고 가독성 떨어지는 코드이기 때문입니다.
이러한 구조로 만들어지는데 많은 원인이 있지만, 단연 try-catch 문이 일등 공신이라고 생각합니다.
가독성이 떨어지는 것부터, try문 안에 있는 것은 지역변수로 처리되는 점, 반환형에 맞게 return을 try-catch문 안팎으로 처리해야된다는 점 등 자연스럽게 프로젝트의 구조를 일그러뜨렸습니다.
물론, try-catch문이 나쁘다는 것은 아니지만, 발생하는 예외를 체계적으로 관리할 필요가 있었습니다.
Spring은 다양한 예외 처리 방법이 있지만, 그 중 @ControllerAdvice와 @RestControllerAdvice 어노테이션을 소개합니다.
@ControllerAdvice와 @RestControllerAdvice
@ControllerAdvice와 @RestControllerAdvice는 전역적으로 예외를 처리할 수 있는 어노테이션입니다. 각각 Srping 3.2, Spring 4.3부터 제공하고 있습니다. @Controller 어노테이션이 붙은 컨트롤러에서 발생하는 예외를 처리할 수 있습니다.
@ControllerAdvice와 @RestControllerAdvice의 차이점
두 어노테이션의 차이는 @RestController와 @Controller와의 차이점과 같습니다.
@RestControllerAdvice의 설명 첫 줄에 다음과 같이 써있습니다.
A convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody."
즉, @RestControllerAdvice는 @ResponseBody가 추가로 붙어있어, 응답을 JSON으로 내려준다는 특징이 있습니다. 해당 어노테이션의 구현부를 보면 다음과 같습니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
~~
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
~~
}
@ControllerAdvice는 컨트롤러에 대해 @ExceptionHandler, @InitBinder, @ModelAttribute가 적용된 메소드에 AOP를 적용하기 위해 고안되었습니다.
또한, @Component가 포함되어 있기 때문에, 빈으로 관리됩니다.
@ExceptionHandler
여기서 말하는 @ExceptionHandler 어노테이션은 AOP를 이용한 예외처리 방식입니다. 메소드에 선언해 예외 처리를 하려는 클래스를 지정하면, 예외 발생 시 정의된 로직에 의해 처리됩니다.
즉, 특정 클래스에서 발생하는 예외를 어떻게 처리할지 정해놓고, 처리할 수 있다는 것입니다.
@RestControllerAdvice와 @ExceptionHandler는 다음과 같이 활용할 수 있습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
>위와 같이 작성하게 되면, 컨트롤러에서 발생하는 Exception.class 및 하위 클래스에 속한 예외가 발생하면, HttpStatus와 함께 메시지를 처리하게 됩니다.
주의할 점은, 프로젝트에 하나의 @ControllerAdvice만 관리하는 것을 권장합니다. 여러 개를 사용하려면, basePackageClasses 및 basePackages와 같은 Selector를 사용해야하기 때문입니다. 이렇게 되면, OR연산을 통해 올바른 Selector를 찾아야 합니다. 해당 과정은 런타임 시점에 수행되므로, 많아질 수록 성능에 영향을 미치고 복잡성이 올라갈 수 있습니다.
또한, 직접 작성한 예외 클래스는 한 곳에서 관리되어야 합니다.
추가로, 예외처리에 대한 우선순위는 @Order 혹은 @Priority를 기준으로 정렬되어 처리됩니다.
정리
이렇게 해당 @ControllerAdvice와 @RestControllerAdvice를 통해 전역적으로 발생하는 예외를 처리하는 방법에 대해 알아보았습니다.
해당 어노테이션을 통해, 각 메소드에 try-catch문으로 처리할 필요가 없어졌고, 발생하는 에러에 대한 응답을 일관성 있게, 본인이 원하는 포맷으로 전달할 수 있게 되었습니다.
'개발 > Spring & Spring boot' 카테고리의 다른 글
[Spring] 내가 ~Service, ServiceImpl로 분리하는 이유 (0) | 2023.03.01 |
---|---|
[Spring] API 문서 자동화를 위한 Swagger 3.0.0 적용 (0) | 2022.06.29 |
[Spring] JPA, RDS, MySQL 연동 시 연결 안됨-CommunicationsException (0) | 2022.04.04 |