예외 처리를 왜 필요할까?
- 발생하는 예외에 따라 응답값을 다른 HTTP Status 값으로 보내줘야 명확히 어떤 예외가 터졌는지 파악 한 후 처리할 수 있다. 그래야 응답값에 따라 메세지/화면을 처리하여 사용자에게 더 나은 사용성을 제공한다. 예외 처리하는 다양한 방법들을 알아보자.
HandlerExceptionResolver 사용하기
- Exception Resolver 동작과정
- 적용 전: Request -> Dispatcher Servlet -> Handler Adaptor -> Dispatcher Servlet -> Reseponse
- 적용 후: Request -> Dispatcher Servlet -> Handler Adaptor -> Exception Resolver -> Response
@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof IllegalArgumentException) {
log.info("IllegalArgumentException resolver to 400");
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
return new ModelAndView();
}catch (IOException e){
log.error("resolver ex", e);
}
return null;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
}
}
- 첫번째 코드처럼 HandlerExceptionResolver를 상속받은 후 resolveException() 메서드를 override해서 예외 로직을 작성해야한다. 후에 WebMvcConfigurer를 상속받은 웹설정 관련 WebConfig클래스에 생성한 예외처리 클래스를 추가해야한다. 만약 처리 할 수 있는 ExceptionResolver가 없으면 Dispatcher Servlet에 예외(HTTP Status 500)을 전달해준다.
@ExceptionHandler 사용하기 - 실무에서 자주 사용하는 방식
- ResponseStatusExceptionResolver는 예외에 따라서 HTTP 상태 코드를 지정해주는 역할을 한다.
- 깔끔한 예외 처리를 위해 ErrorResult 같은 에러 응답값을 위한 클래스를 만든다. @ExceptionHandler에 적용한 후 원하는 예외 클래스를 넣어 준후 인자 값에 해당 예외 클래스를 받는다. 그리고 ErrorResult에 인자로 받은 예외 클래스에 메세지와 상태 코드를 넣어 준 후 리턴해준다. @ResponseStatus(HttpStatus.BAD_REQUEST)를 지정해주지 않으면 예외가 터져도 HTTP 상태코드가 200(성공)이 된다. Exception 처리는 @ExceptionHandler가 가지고있는 해당 예외 클래스만 적용이된다.
@ExceptionHandler
public ResponseEntity<ErrorResult> illegalExHandlerWithResponseEntity(IllegalArgumentException e){
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("error", e.getMessage());
return new ResponseEntity(errorResult,HttpStatus.BAD_REQUEST);
}
@ExceptionHandler
public ResponseEntity<ErrorResult> exceptionHandler(Exception e){
ErrorResult errorResult = new ErrorResult("error", "내부 오류");
return new ResponseEntity(errorResult,HttpStatus.INTERNAL_SERVER_ERROR);
}
- 위와 같이 ResponseEntity로 감싸준 후 HttpStatus code를 지정해주멸 @ResponseStatus()를 사용안해도 된다.
@ExceptionHandler(부모예외)
public String parent(부모 예외 e)
@ExceptionHandler(자식예외)
public String child(자식 예외 e)
- 주의점: Spring은 항상 자식 객체에 우선권을 가진다. 자식 클래스가 예외가 터지면 자식예외도 처리하고 부모 예외도 처리한다. 하지만 부모클래스가 예외가 터지면 부모예외만 처리된다.
@ControllerAdvice 사용해서 전역적 처리
- @ExceptionHandler 를 사용하면 예외처리는 가능하지만 해당 어노테이션이 달려있는 컨트롤러 내에서만 동작이 가능하므로 확장적이지 않다. @ControllerAdvice를 통해 전역적으로 또는 설정해서 원하는 패키지까지 예외처리를 핸들링 할 수 있다. @RestControllerAdvice는 @ResponseBody + @Controller와 같은 기능이다.
@RestControllerAdvice("web.exception")
public class ControllerAdvice {
@ExceptionHandler
public ResponseEntity<ErrorResult> illegalExHandlerWithResponseEntity(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("error", e.getMessage());
return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler
public ResponseEntity<ErrorResult> exceptionHandler(Exception e) {
ErrorResult errorResult = new ErrorResult("error", "내부 오류");
return new ResponseEntity(errorResult, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
- 위와같이 @RestControllerAdvice()안에 패키지를 지정할 수 있다. 패키지를 지정안하면 glboal(전역)으로 적용이된다.
결론
- 좀 더 다양하게 예외처리하는 방법을 알았고 각각의 장단점을 알게 되었다. 안정적인 서비스를 위해서 상태에 알맞은 예외처리도 중요하다.
reference
- 김영한님의 인프런강의[스프링 MVC 패턴 2]
'Java' 카테고리의 다른 글
[Spring] Servlet Filter란? (0) | 2023.03.27 |
---|---|
[Spring] 싱글톤 컨테이너란? (3) | 2023.03.13 |
[JAVA] JUnit 5 사용해보자! (0) | 2023.02.27 |
[Spring] @Autowired를 사용하지말고 다양한 의존성 주입을 알아보자 (0) | 2023.01.25 |
[JAVA] Process와 Thread - 일해라 일! (0) | 2023.01.22 |