Post

[Spring Boot] 에러를 효과적으로 처리하는 2가지 방법(@ExceptionHandler, @ControllerAdvice 사용)

[Spring Boot] 에러를 효과적으로 처리하는 2가지 방법(@ExceptionHandler, @ControllerAdvice 사용)

왜 필요해요?

프로젝트를 진행하다 보면 에러 응답 포맷을 변경해야 하는 상황이 생길 수 있습니다. 예를 들어, 고객 요구사항 변경, 공통 응답 규격 개편, 코드 리팩터링 등..

이때 모든 컨트롤러에 동일한 보일러플레이트 코드가 존재해서 수정할 때 유지보수 비용이 커집니다.

try-catch 문

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
public class TryCatchController {

  @GetMapping("/tryCatch")
  public String tryCatch() {
    try {
      throw new IllegalArgumentException("IllegalArgument 에러");
    } catch (Exception e) {
      return "에러가 발생했습니다. : " + e.getMessage();
    }
  }

  @GetMapping("/tryCatch2")
  public String tryCatch2() {
    try {
      throw new RuntimeException("Runtime 에러");
    } catch (Exception e) {
      return "에러가 발생했습니다. : " + e.getMessage();
    }
  }

}

위와 같이 공통적인 에러를 처리할 때 각 메서드마다 try-catch 구문을 작성하는 방식은 실제로 간단하지만, 프로젝트의 규모가 커질수록 유지보수가 어려워지는 예시를 보여주고 있습니다.

그렇다면, @ExceptionHandler@ControllerAdvice 를 같이 사용하면 어떻게 될까요?

@ExceptionHandler + @ControllerAdvice 사용

1
2
3
4
5
6
7
@ControllerAdvice
public class ErrorHandler {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception e) {
    return "에러가 발생했습니다. : " + e.getMessage();
  }
}

Exception 에러 처리를 공통적으로 한번 묶어서 사용하니 이전보다 깔끔해 졌습니다.

이제 @ExcpetionHandler@ControllerAdvice 가 무엇이고, 어떤 역할을 하는지 각각 알아보겠습니다.

@ExceptionHandler 란?

Spring MVC에서 컨트롤러 메서드에서 발생한 예외를 처리하기 위해 사용하는 어노테이션입니다.
선언한 컨트롤러에서만 발생하는 예외에 대해 처리한다고 보시면 됩니다.

@ExceptionHandler 예시코드

1
2
3
4
5
6
7
8
9
@GetMapping("/exceptionHandler")
  public String exceptionHandler() {
    throw new IllegalArgumentException("IllegalArgument 에러");
  }

  @ExceptionHandler(IllegalArgumentException.class)
  public String handleIllegalArgument(IllegalArgumentException e) {
    return "에러 발생: " + e.getMessage();
  }



@ControllerAdvice 란?

Spring MVC에서 모든 컨트롤러에 전역적으로 적용할 수 있는 기능을 제공하는 어노테이션입니다.

에러 처리 외에도 데이터 바인딩, 전역 모델 속성도 추가할 수 있지만
에러 처리외 나머지 기능은 다른 글을 통해 소개할게요.

@ControllerAdvice 예시코드

1
2
3
4
5
6
7
@ControllerAdvice
public class ErrorHandler {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception e) {
    return "ControllerAdvice 에러가 발생했습니다. : " + e.getMessage();
  }
}

위 코드는 @ExceptionHandler + @ControllerAdvice 사용에서 보셨던 코드와 동일합니다.

에러 처리 순서에 대해 주의하세요!

@ExceptionHandler@ControllerAdvice를 사용할 때, 주의할 내용이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class ExceptionHandlerController {

  @GetMapping("/exceptionHandler")
  public String exceptionHandler() {
    try {
      throw new IllegalArgumentException("IllegalArgument 에러");
    } catch (Exception e) {
      return "여기서 동작해요";
    }
  }

  @ExceptionHandler(IllegalArgumentException.class)
  public String handleIllegalArgument(IllegalArgumentException e) {
    return "여기서 동작하지 않아요";
  }
}

위에 처럼 try-catch를 같이 쓰게 된다면 어떻게 될까요?

아무리 @ExceptionHandler, @ControllerAdvice 를 사용한다고 해도 컨트롤러 메서드 안에 try-catch 를 사용한다면 try-catch가 우선적으로 적용됩니다.

@RestControllerAdvice 란?

@ControllerAdvice@RestControllerAdvice 도 있습니다. @RestControllerAdvice는 단순하게 @ControllerAdvice@ResponseBody 를 합친 어노테이션입니다.

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice

개인적으로 실무에서는 @RestControllerAdvice를 주로 사용하고 있어요.

예외 처리하는 우선순위 요약

우선순위가 높은 순서대로 나열했습니다.

  1. 메서드 내부의 try-catch
  2. @ExceptionHandler(컨트롤러 내부 정의)
  3. @ControllerAdvice or @RestControllerAdvice

@Order 사용으로 순서 정하기

만약, 아래처럼 2개의 @ControllerAdvice가 존재하면 어떻게 될까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ControllerAdvice
public class ErrorHandler {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception e) {
    return "ControllerAdvice 에러가 발생했습니다. : " + e.getMessage();
  }
}

@ControllerAdvice
public class ErrorHandler2 {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception e) {
    return "ControllerAdvice2 에러가 발생했습니다. : " + e.getMessage();
  }
}

@Order를 사용하면 여러 개의 @ControllerAdvice, @RestControllerAdvice가 존재할 때 순서를 정할 수 있습니다.
(예외 처리하는 우선순위 요약에 해당하는 1, 2번은 불가능)

@Order 의 설명을 보면, Lower values have higher priority. 로 되어 있습니다.
즉, 작은 값은 큰 우선순위를 갖는다.라고 해석할 수 있으며,
아래처럼 ErrorHandler를 1순위로 줄 수 있습니다.

1
2
3
4
5
6
7
8
@ControllerAdvice
@Order(1)
public class ErrorHandler {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception e) {
    return "ControllerAdvice 에러가 발생했습니다. : " + e.getMessage();
  }
}

github 예시 주소

github 주소 : https://github.com/JacksonJang/SpringBootErrorHandlerExample

This post is licensed under CC BY 4.0 by the author.