@ExceptionHandler, @ControllerAdvice

2022. 3. 8. 23:59Spring

반응형

안녕하세요. 오늘은 Spring에서 Exception 처리에 대해 다뤄보겠습니다.

지난 포스팅에서 Spring Exception에 대한 내용을 다뤘습니다. 

이번 포스팅에서는 Annotation을 이용한 Exception 처리 방식에 대해 다루고자 합니다.

 

@ExceptionHandler, @ControllerAdvice을 알아볼텐데요.

미리 간단히 정리하면 아래와 같습니다.

 

미리 보기 ✔️

@ExceptionHandler : Controller 내의 Method 범위로 Exception 처리 (Target이 Method)

@ControllerAdvice   : Controller 전역에 걸쳐 Exception 처리 (Target이 TYPE  − 즉, Class, Interface, Enum)

 

 

 


 

@ExceptionHandler

🔗 Spring.io - ExceptionHandler

@Target(value=METHOD)
 @Retention(value=RUNTIME)
 @Documented
public @interface ExceptionHandler {...}

 

@ExceptionHandler는 Controller 내 메소드에 정의되어 해당 컨트롤러에서 발생하는 (특정 혹은 모든) 에러를 받아 처리합니다.

 

HttpServletRequest, HttpServletResponse, HttpSession 등을 인수로 받기 때문에

@Controller, @RestController 와 같은 컨트롤러 내에서 사용 가능합니다.

 

또, 반환타입은 ModelAndView, View, String, void, Model 등이 될 수 있습니다.

 

실제 예시를 확인해보면 아래와 같습니다.

 

@RestController
public class TestController { 
    ...
    @ExceptionHandler 
    public Object handleException(Exception e) { 
        return "error"; 
    }
}

 

TestController 내에서 던져진 예외를 처리하는 메서드를 @ExceptionHandler가 정의된 handleException이 받아서 처리합니다.

위의 경우에는 error.jsp를 띄어주는 역할을 할 수 있겠죠?

 

 

✔️ Sample Code

@RestController
public class TestController { 
    ...
    // TestController 내 범위에서 IOException이나 FileNotFoundHandler가 발생하면 notFoundException이 처리한다.
    @ExceptionHandler(value = {IOException.class, FileNotFoundException.class})
    public Object notFoundHandler(Exception e) { 
        return "notfound"; 
    }
    // TestController 내 범위에서 TimeoutException가 발생하면 timeoutHandler가 처리한다.
    @ExceptionHandler(value = TimeoutException.class)
    public Object timeoutHandler(Exception e) { 
        return "error"; 
    }
}

 

위와 같이 value 속성으로 @ExceptionHandler 가 처리할 특정 예외를 지정할 수도 있습니다.

IOException이나 FileNotFoundException이 발생한다면 notFoundHandler에서 처리되고, 

TimeoutException이 발생한다면 timeoutHandler가 처리합니다.

 

물론, TestController 내라는 범위에 한해서 말이죠.

 

 

 

이러한 ExceptionHandler는 HandlerExceptionResolver에 의해 처리됩니다.

 

 

출처 : https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/

 

HandlerExceptionResolver는 발생한 예외를 @Exception이 설정된 메서드로 매핑해주는 역할을 합니다.

 

 

 

 

HandlerException은 Method라는 범위를 가지고 있습니다.

 

그럼, 더 넓은 범위는 없을까요?

이런 질문을 하는 거 보니 있어 보이죠,,ㅎㅎ

네, 바로 ControllerAdvice가 전역적인 핸들링 역할을 할 수 있습니다.

 

 

 

@ControllerAdvice

🔗 Spring.io - ControllerAdvice

 

@Target(value=TYPE)
 @Retention(value=RUNTIME)
 @Documented
 @Component
public @interface ControllerAdvice

 

@ControllerAdvice는 모든 @Controller 전역에서 발생할 수 있는 예외를 잡아 처리해주는 annotation입니다.

위의 spring 공식 사이트에서 소개하는 글을 잠시 참고해볼게요.

 

Specialization of @Component for classes that declare @ExceptionHandler, @InitBinder, or @ModelAttribute methods to be shared across multiple @Controller classes.

 

@ExceptionHandler, @InitBinder 또는 @ModelAttribute 로 정의된 메서드가,

여러 @Controller 클래스에 공유되도록 선언한 특별한 @Component 클래스입니다.

 

즉, @ExceptionHandler, @InitBinder, @ModelAttribute 를 여러 곳에서 사용하고 싶어서

한 곳에 정의해두고 필요할 때 사용할 수 있게끔 만들어둔 클래스(Advice)인 것이죠.

 

예외처리만을 위한 건 아니고, 특별히 예외처리에 많이 사용되어 언급되는 Annotation입니다.

 

어드바이스(Advice) ?
 : 타깃 오브젝트(Target Object)에 적용할 부가 기능을 담은 오브젝트
   , 메인 업무에 보조적으로 추가될 보조 업무

 

 

@ControllerAdvice과 함께 정의된 Class는 Spring bean이나 클래스 경로 검색을 통해 자동으로 탐지될 수 있는데요.

그 범위를 basePackageClasses, basePackages 속성을 이용하여 특정 클래스에서 발생하는 예외만 받아서 처리할 수 있습니다.

EX, @ControllerAdvice(basePackages= "my.packages") or @ControllerAdvice(basePackageClasses= MyClass.class)

 

 

설명보다는 코드가 더 이해하기 쉬울 것 같네요.

주석을 따라 잘 살펴보면 이해하기 편할 것입니다.

 

✔️ Sample Code

@RestControllerAdvice 
public class GlobalExceptionHandler { 

    // 기존에 정의된 Exception 처리
    @ExceptionHandler(java.lang.IllegalArgumentException.class)
    public ResponseEntity<Object> IllegalArgumentException(java.lang.IllegalArgumentException.class) {
    	// IllegalArgumentException 만을 처리하기 위한 코드
    }

    // Custom Exception 처리
    @ExceptionHandler(com.gngsn.demo.error.BadParameterException)
    public ResponseEntity<Object> custom() {
    	// 400 status code를 반환하는 코드
    }
    
    // 최상위 에러 처리
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> RootException(Exception ex) {
        // ex. error log 처리, 개발자에게 메일 전송 등
    }

}

 

 

 

✔️ 동작 원리

@ControllerAdvice이 어떻게 실행되는지, 그 동작원리를 살펴보겠습니다.

 

Spring MVC의 동작원리를 살펴보면 DispatcherServlet에 의해 처리되는 것을 볼 수 있는데요.

 

DispatcherServlet에서 실행(doDispatch)을 하다가 에러가 발생하면(exception != null) 

@ControllerAdvice로 정의된 빈이 주입된 handlerExceptionResolvers가 실행되는 구조입니다.

 

 

더 깊게 아실 분들만 아래 코드를 확인하면 될 듯합니다.

 

public class DispatcherServlet extends FrameworkServlet {
   // ① doDispatch - processDispatchResult 호출
   protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
      // ...
      processDispatchResult(/* 생략 */);
      // ... 
   }
  
   // ② processDispatchResult - exception 발생 시 processHandlerException 호출
   private void processDispatchResult( /* 생략 */ ) throws Exception {
       ...
      if (exception != null) {
         // ...
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
         // ...
      }
      // ...
   }

   // ③ processHandlerException - handlerExceptionResolvers 실행
   protected ModelAndView processHandlerException(/* 생략 */) throws Exception {
      // ...

      // Check registered HandlerExceptionResolvers...
      ModelAndView exMv = null;
      if (this.handlerExceptionResolvers != null) {
         for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            // ...
         }
         //...
      }
   }
}

 

 

DispatcherServlet 요약

① doDispatch - processDispatchResult 호출

② processDispatchResult - exception 발생 시 processHandlerException 호출

③ processHandlerException - handlerExceptionResolvers 실행

handlerExceptionResolvers는 @ControllerAdvice로 정의된 빈이 주입됩니다.

 

 

 

 

그럼 여기까지 Spring에서 예외를 처리하는 방법에 대해 다뤄보았습니다.

추가할 내용이나 첨언, 혹은 오타나 잘못된 내용이 있다면 댓글로 남겨주세요!

감사합니다 ☺️ 

 

 

반응형

'Spring' 카테고리의 다른 글

Spring Interceptor, 제대로 이해하기  (6) 2022.03.15
Spring Transaction, 제대로 이해하기  (0) 2022.03.13
Spring Exception, 제대로 처리하기  (0) 2022.03.06
Spring, 3-Tier-Architecture  (0) 2021.05.06
Spring MVC, 동작 원리  (4) 2021.05.02

Backend Software Engineer

Gyeongsun Park