프로그래밍 공부/Spring

[Spring] @ExceptionHandler 와 @ControllerAdvice

예외처리

프로그래밍에서 예외처리는 매우 중요하다. 

사실 우테코에 들어오기 전에는 예외처리와 관련된 코드를 매우 세세하게 짜는 편은 아니었다. 그냥 돌아가면 되는 거지! 정도의 코드였다. 다만, 상세하고 다양한 예외를 갖출 수 있게 된다면 더 안정적인 프로그램이 만들어질 수 있다.

 

그렇지만 try-catch나 if문으로 예외를 잡거나 상위메서드로 예외처리를 위임하는 등의 작업을 진행하더라도 코드는 쉽게 복잡해질 수 있고, 이는 비즈니스 로직보다 예외처리를 위한 코드에 더 힘쓰게 되는 결과를 초래한다.

리뷰어의 코멘트

 

나 역시 해당 부분에 있어서 잘 모르는 부분이 존재했고, 또한 기존 Spark 구현 코드를 Spring으로 이식하는 과정에서 예외처리와 관련된 문제들을 해결하기 위해 @ExceptionHandler와 @ControllerAdvice라는 키워드를 조언받았기에, 이에 대해서 간단하게 정리해보고자 한다.

 

 

@ExceptionHandler

@ExceptionHandler의 경우는 @Controller, @RestController가 적용된 Bean 내에서 발생하는 예외를 잡아 하나의 메서드에서 처리하는 기능을 제공한다.

 

@RestController
public class RestController { 
	//......

	// 하나만 지정할 경우 
    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<String> myException(Exception e) {
        //...
    }
    
	// 여러개 지정도 OK 
    @ExceptionHandler({FileSytemException.class, RemoteException.class})
    public ResponseEntity<String> myException2(Exception e) {
        //...
    }
}

주의사항/알아 둘 것

  • Controller, RestController에만 적용 가능하다. (@Service 같은 빈에서는 적용되지 않는다.)
  • 리턴 타입은 자유롭게 해도 된다. (Controller내부에 있는 메서드들은 여러 타입의 response를 할 것이다. 해당 타입과 전혀 다른 리턴 타입이어도 상관없다.) 공식문서에 나와있는 반환 값 링크
  • 메서드의 파라미터로 Exception을 받아왔는데 이것 또한 자유롭게 받아와도 된다. 공식문서의 메서드 인수 링크
  • @ExceptionHandler를 등록한 Controller에만 적용된다. 다른 Controller에서 NullPointerException이 발생하더라도 예외를 처리할 수 없다.

 

여기서 마지막 사항을 눈여겨보자. @ExceptionHandler만 존재할 경우, 해당 Controller에서만 적용된다는 단점이 존재한다. 즉, 비슷한 에러를 처리하기 위한 비슷한 코드가 중복으로 구성될 수 있다는 것이다. 이를 해결할 수 있는 방법이 바로 @ControllerAdvice다.

 

 

 

@ControllerAdvice와 @RestControllerAdvice

Spring은 앞서 말한 @ExceptionHandler의 한계를 극복하는 일환으로 전역적으로 예외를 처리하는 @ControllerAdvice와 @RestControllerAdvice를 각각 Spring3.2, Spring4.3부터 제공하고 있다.

 

각각의 차이는 앞에 Rest가 붙었다는 점에서 눈치챌 수 있듯, @Controller와 @RestController의 차이와 유사하다. (관련 글 - [Spring] @Controller와 @RestController)

즉, @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를 동일하게 적용해준다. 

 

 

ControllerAdvice의 장점

  • 하나의 클래스로 모든 컨트롤러에 대해 전역적으로 예외 처리가 가능함
  • 직접 정의한 에러 응답을 일관성 있게 클라이언트에게 내려줄 수 있음
  • 별도의 try-catch문이 없어 코드의 가독성이 높아짐

 

ControllerAdvice의 주의점

  • 한 프로젝트당 하나의 ControllerAdvice만 관리하는 것이 좋다.
  • 만약 여러 ControllerAdvice가 필요하다면 basePackages나 annotations 등을 지정해야 한다.
  • 직접 구현한 Exception 클래스들은 한 공간에서 관리한다.

 

 

참고자료

https://tecoble.techcourse.co.kr/post/2021-05-10-controller_advice_exception_handler/

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler

https://jeong-pro.tistory.com/195

https://mangkyu.tistory.com/204