-
Spring의 예외처리(3) - Spring 전역에서 예외 처리해보자Springboot/study 2023. 11. 30. 15:46
✍️ 전역에서 예외 처리
1. Why?
전역에서 예외처리를 하는 이유는 무엇일까요?
프로젝트의 모든 Controller 단의 예외 처리 코드를 아래 코드와 같이 각 Controller 안에서 직접 예외 코드를 작성한다고 고려해봅시다.
프로젝트의 규모가 커진다면,
각 예외 처리에 대한 중복 코드 또한 늘어날 것이고, 컨트롤러의 개수가 늘어남에 따라 각 컨트롤러에 들어갈 예외 처리 코드를 유지보수할 생각에 벌써부터 암울해집니다.🥲
@RestController public class ImageController { ... @ExceptionHandler({MaxUploadSizeExceededException.class, SizeLimitExceededException.class, MissingServletRequestPartException.class, MultipartException.class}) protected ResponseEntity<ErrorResponse> imageFileSize(Exception e) { ... } @ExceptionHandler(CustomException1.class) protected ResponseEntity<ErrorResponse> handleCustomException1(final CustomException1 error) { ... } @ExceptionHandler(CustomException2.class) protected ResponseEntity<ErrorResponse> handleCustomException2(final CustomException2 error) { ... } }2. 전역 처리를 해보자
@RestControllerAdvice와 @ControllerAdvice
두 어노테이션 중 하나를 가진 클래스를 만든다면, Application 전역에 발생하는 모든 예외를 한 곳에서 관리할 수 있습니다.
참고로, 이 둘은 @RestController가 @Controller+@ResponseBody인 듯, @RestControllerAdvice는 @ControllerAdvice+@ResponseBody 입니다.
실제로 프로젝트를 진행하며 적용하였던 코드입니다.
아래와 같이, 컨트롤러단에서 발생하게 되는 각 예외들을 GlobalExceptionHandler라는 하나의 클래스로 관리합니다.
모든 컨트롤러에서 공통으로 발생하는 관심사인 예외 처리를 하나의 클래스로 분리하여 처리함으로써 AOP(관점 지향 프로그래밍)의 구현 방식에 부합하게 됩니다.
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { // 비즈니스 예외 처리시 발생 @ExceptionHandler protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) { return ErrorResponse.toResponseEntity(e.getErrorCode()); } // javax.validation.Valid or @Validated 으로 binding error 발생시 발생 // HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생 @ExceptionHandler(MethodArgumentNotValidException.class) protected ResponseEntity<ErrorResponse> methodArgumentValidation(MethodArgumentNotValidException e) { log.error("handleMethodArgumentNotValidException", e); return ErrorResponse.toResponseEntity(ErrorCode.INVALID_REQUEST_PARAMETER); } // @ModelAttribute 으로 바인딩 에러시 발생 @ExceptionHandler(BindException.class) protected ResponseEntity<ErrorResponse> bindException(BindException e) { log.error("handleBindException", e); return ErrorResponse.toResponseEntity(ErrorCode.INVALID_REQUEST_PARAMETER); } ...@(Rest)ControllerAdvice와 @ExceptionHandler
위 코드에서 알 수 있듯, @ExceptionHandler를 사용하여 value 값으로 특정 예외 클래스를 지정해줌으로써, 해당 에러를 잡을 수 있게 됩니다.
@ExceptionHandler는 컨트롤러 단의 예외를 처리해준다고 하였습니다.
그럼 @ControllerAdvice, @RestControllerAdvice 안에서는 어떻게 사용할 수 있게 되는 걸까요?
@ControllerAdvice는 @Controller와 handler에서 발생하는 에러들을 모두 잡아줍니다.
결론적으로, 컨트롤러단의 예외를 처리하는 @ExceptionHandler는 @(Rest)Controller와 핸들러를 모두 처리해주는 @(Rest)ControllerAdvice에서 사용할 수 있게되는 것입니다!!!😄
🤔 그럼 @RestController는 @RestControllerAdvice가 잡아주고, @Controller는 @ControllerAdvice가 잡아주는 것일까요?
결론부터 이야기하자면, No 입니다!!
@Controller에서 예외가 발생해도 @RestControllerAdvice에서 잡을 수 있고, @RestController에서의 예외가 발생해도 @ControllerAdvice가 잡아줄 수 있습니다.
다음 코드를 봅시다.
우선, @ControllerAdvice가 붙여져 있는 GlobalExceptionHandler입니다.
@ControllerAdvice public class GlobalExceptionHandler { // 지원하지 않은 HTTP Method 호출 할 경우 발생 @ExceptionHandler(HttpRequestMethodNotSupportedException.class) protected ResponseEntity<ErrorResponse> requestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { return ErrorResponse.toResponseEntity(ErrorCode.INVALID_METHOD_TYPE); } }다음으로, @RestController가 붙어있는 컨트롤러단의 StoreController입니다.
@RestController public class StoreController { @GetMapping("/some") public int deletePost() { return 1; } }만약, @ControllerAdvice가 @RestController에서 발생하는 예외를 잡지 못한다면, 같은 요청 url로 GET이 아닌 POST의 요청을 하였을 때 기존 Spring에서 설정되어 있는 HttpRequestMethodNotSupportedException 예외 처리될 것입니다.
Postman을 활용한 실행 결과입니다.
포스팅에서는 첨부하지 않았지만, 제가 따로 설정해둔 ErrorResponse의 형태로 나오는 것을 확인할 수 있습니다.

Why?
쉽게 생각합시다.
@RestController와 @RestControllerAdvice는 @Controller와 @ControllerAdvice에 @ResponseBody를 추가한 것뿐입니다.
// @RestController @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { // ... } // @RestControllerAdvice @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @ControllerAdvice @ResponseBody public @interface RestControllerAdvice { // ... }따라서, 서로 예외를 잡아 줄 수는 있습니다.
하지만 @RestControllerAdvice는 @RestController와 마찬가지로 @ResponseBody가 있어서 자바 객체를 Json/Xml 형태로 반환하여 응답 형식을 HTTP Response Body에 담게 되는 것입니다.
참고자료
[스프링부트] @ExceptionHandler를 통한 예외처리
@ExceptionHandler는 Controller계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능이다.Service, Repository에서 발생하는 에러는 제외한다.간단한 예시부터 살펴보자.이렇게 @Controller로 선언된 클
velog.io
https://tecoble.techcourse.co.kr/post/2020-07-28-global-exception-handler/
Spring에서 전역 예외 처리하기
Spring에서 예외 처리하는 방법은 여러 가지가 있다. 메서드에서 try/catch…
tecoble.techcourse.co.kr
[스프링부트] @ExceptionHandler를 통한 예외처리
@ExceptionHandler는 Controller계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능이다.Service, Repository에서 발생하는 에러는 제외한다.간단한 예시부터 살펴보자.이렇게 @Controller로 선언된 클
velog.io
'Springboot > study' 카테고리의 다른 글
Spring의 예외처리(5) - ErrorCode를 직접 정의해보자! (0) 2023.12.01 Spring의 예외처리(4) - 표준 예외 vs 사용자 정의 예외 (0) 2023.11.30 Spring의 예외처리(2) - Spring의 예외처리 흐름과 다양한 예외처리 방식 (1) 2023.11.30 Spring의 예외처리(1) - 예외 종류 (0) 2023.11.30