2022. 3. 8. 23:59ㆍSpring
안녕하세요. 오늘은 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에 의해 처리됩니다.
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
𝐒𝐮𝐧 · 𝙂𝙮𝙚𝙤𝙣𝙜𝙨𝙪𝙣 𝙋𝙖𝙧𝙠