개발 일기

[Spring Boot] 예외처리 및 @RestControllerAdvice & @ExceptionHandler 본문

Back-End/Spring

[Spring Boot] 예외처리 및 @RestControllerAdvice & @ExceptionHandler

개발 일기장 주인 2024. 9. 15. 00:31

아키텍쳐를 멀티 모듈로 구상하며 하나하나 다시 코드를 뜯어보는 도중에 예외처리한 부분이 좀 아쉽다는 생각이 들어 다시 래퍼런스를 찾아보며 다시 뜯어보기로 했다. 

 

우선 예외처리에 대해서 다시한번 짚고 넘어가보자.

 

예외처리(Exception Handling)

예외처리란, 프로그램 실행 시 발생할 수 있는 얘기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며,
예외처리를 하는 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스러운 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.

프로그램 에러
- 컴파일 에러  : 컴파일 시에 발생하는 에러
- 런타임 에러 : 프로그램 실행 시에 발생하는 에러
- 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것

자바에서 런타 에러는 에러(Error)와 예외(Exception)으로 구분 가능하다.
- 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
   ex) 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)
- 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

 

모든 예외 클래스는 Throwable 클래스를 상속 받고 있고 Exception은 수많은 자식 클래스가 있고 RuntimeException은 Unchecked Exception이며, 그 외 Exception은 Checked Exception으로 볼 수 있다.

  Checked Exception Unchecked Exception
처리여부 반드시 예외 처리 필요 명시적 처리 강제하지 않음
확인시점 컴파일 단계 실행 중 단계
예외발생 시 트랜잭션 롤백하지 않음 롤백함
대표 예외 IOException
SQLException
RuntimeException(
NullPointerException,
Illegal ArgumentException,
IndexOutOfBoundException,
SystemException
)

 

잠시 Throwable을 보면

Exception의 생성자를 보면 String으로 message를 받게되고 super()를 통해 파라미터를 Throwable로 넘기는 것을 확인할 수 있다.

Throwab에서 그 message를 받고 detailMessage라는 필드에 저장된다.

Exception을 상속 받는 AroundHubException을 보자.

HttpStatus는 Enum클래스 이고

이것을 사용하는 Custom Exception을 보면

  • error type: HttpStatus의 reasonPhrase
  • error code: HttpStatus의 value
  • message: 상황별 디테일 Message

 

 

 

@ControllerAdvice, @RestControllerAdvice 

스프링 부트에서 Controller 단의 예외 처리 방식은 크게 두 가지로 볼 수 있다.

  1. @ControllerAdvice를 통한 모든 Controller들에서 발생되는 예외처리
  2. @ExceptionHandler를 통한 특정 Controller의 예외처리
@ControllerAdvice로 모든 컨트롤러에서 발생할 예외를 정의하고, @ExceptionHandler를 통해 발생하는 예외 마다 처리할 메소드를 정의하는 것이다.

https://velog.io/@hanblueblue/Spring-ExceptionHandler
@RestControllerAdvice vs @ControllerAdvice


@ControllerAdvice는 Spring에서 제공하는 어노테이션으로 @Controller나 @RestController에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 어노테이션이다. 설정을 통해 범위 지정이 가능하며 Default로는 모든 Controller에 대한 예외 처리를 관리한다.

예외 발생 시 Json의 형태로 결과를 반환해야 한다면 @ResponseBody가 추가적으로 붙어있는 @RestControllerAdvice를 사용한다.

@ExceptionHandler

예외 처리 상황이 발생하면 Handler로 처리하겠다고 명시하는 어노테이션으로 어노테이션 뒤에 괄호를 붙혀서 어떠한 ExceptionClass를 통해 처리할지 설정할 수 있다.

Exception.class는 최상위 클래스로 하위 세부 예외 처리 클래스로 설정한 핸들러가 존재하면 그 핸들러가 우선처리하게 되며, 처리되지 못하는 예외처리에 대해 ExceptionClass에서 핸들링한다.

@ControllerAdvice로 설정된 클래스 내에서 메소드로 정의할 수 있지만, 각 Controller안에도 설정가능하다.

전역(@ControllerAdvice)으로 설정된 것보다 지역 설정(Controller에 @ExceptionHandler)으로 정의한 Handler가 우선순위에 있다.

예외처리 우선순위

 

 

JwtAuthenticationFilter에서 JWT 토큰 검증 후 그 결과에 따라 예외를 처리하기 위해 예외를 JwtCustomException으로 던지고, 이를 @RestControllerAdvice에서 @ExceptionHandler(JwtCustomException.class)로 처리하려 했다. 그러나 이 접근 방식이 작동하지 않아 4시간 동안 고민했는데 안된 이유를 알고나서 너무 현타가 왔다.
너무 당연한것이였는데 내가 너무 간과했다.
바로 필터에서 발생한 예외가 컨트롤러의 예외 처리 흐름에 도달하지 않기 때문에 필터에서 예외를 직접 처리해야 했던 것이다.
즉, 다시말해  @RestControllerAdvice은 오직 Controller Layer의 예외만 공통으로 처리가능하지 JwtAuthenticationFilter와 같이 필터 계층에서 동작하는 필터는 적용되지 않는 것이였다...

https://github.com/JuseungL/modularize-project