Spring - 김영한 MVC 2편 정리 (2)

choko's avatar
Jun 30, 2024
Spring - 김영한 MVC 2편 정리 (2)
 
 

예외 처리와 오류 페이지

서블릿 예외처리

스프링을 사용하지 않은, 서블릿은 다음 2가지 예외 처리를 지원한다.
  • Exception
    • 자바 직접 실행
      • main 쓰레드가 생성되고, main()을 넘어 예외가 던져지면, 예외 정보를 남기고 해당 쓰레드는 종료된다.
    • 웹 애플리케이션
      • try-catch로 예외를 잡지 못하고, WAS까지 예외가 전달될 수 있다.
        • WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
        • 이런경우 톰캣이 기본적으로 제공하는 오류 화면이 노출된다 HTTP Status 404 – Not Found HTTP Status 500 – Internal Server Error
 
  • response.sendError(statusCode [, Error Message])
    • 서블릿 컨테이너에게 오류가 발생했다는 점을 전달한다.
      • response.sendError(404, "404 오류!");
      • response.sendError(500);
    • WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러
 
  • 오류 페이지는 다음과 같은 흐름으로 보여진다.
      1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
      1. WAS /error-page/500 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/
      500) -> View
    • 이때 오류 정보를 request의 attribute에 추가하여 넘겨준다.
 

서블릿 예외처리 - 필터(dispatcherTypes)

  • 오류 페이지를 보여줄때, 이미 완료한 필터 호출이 또 일어나면 비효율적이다. 이를 막기 위해, 서블릿은 dispatcherTypes라는 옵션을 제공한다.
    • public enum DispatcherType { FORWARD, // 서블릿에서 다른 서블릿이나 JSP를 호출할 때 INCLUDE, // 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때 REQUEST, // 클라이언트 요청 ASYNC, // 서블릿 비동기 호출 ERROR // 오류 요청 }
    • 필터의 dispatcherTypes의 디폴트값은 DispatcherType.REQUEST이다. 즉 클라이언트 요청 시에만 적용된다.
    • 만약 에러 발생 시에만 동작하는 필터를 원할 경우, 다음처럼 설정할 수 있다.
      • filterRegistrationBean.setDispatcherTypes(DispatcherType.ERROR)
       

서블릿 예외처리 - 인터셉터

  • 필터의 경우 DispatcherType를 사용할 수 있지만, 인터셉터의 경우 서블릿 제공이 아닌 스프링 제공이기 때문에 DispatcherType와 무관하게 항상 호출된다.
  • 따라서 인터셉터는 오류 페이지 경로를 excludePathPatterns에 추가해서 처리해주자.
    • @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()) .order(1) .addPathPatterns("/**") .excludePathPatterns("/css/**", "/*.ico" , "/error", "/error-page/**" //오류 페이지 경로 ); }
       

스프링 부트 제공 오류 페이지

  • 스프링 부트는 기본적으로 /error 경로로 자동으로 ErrorPage를 등록한다.
  • BasicErrorController
    • 스프링 부트에서 기본적으로 제공하는 예외 처리 컨트롤러
    •  
 

API 예외 처리

HandlerExceptionResolver

  • HandlerExceptionResolver는 예외가 던져진 경우 해당 예외를 해결하고, 동작을 새로 정의할 수 있는 방법을 제공한다.
notion image
  • HandlerExceptionResolver 인터페이스
    • public interface HandlerExceptionResolver { ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
    • resolveException으로 ModelAndView를 반환한다. 예외처리 후 정상 흐름처럼 변경하기 위함이다.
      • ModelAndView가 비워있으면
        • View 랜더링 업이 정상 흐름으로 서블릿이 리턴됨
      • ModelAndView 지정 시
        • View를 랜더링하여 리턴한다
      • null
        • null 반환 시 다음 ExceptionResolver을 찾아 실행한다.
  • HandlerExceptionResolver를 다음과 같이 활용할 수 있다.
    • 예외 상태 코드 반환
    • View 템플릿 처리
    • API 응답 처리
    • 예외를 여기까지만 처리
      • WAS까지 예외가 도달하는것이 아닌, ExceptionResolver에서 예외를 처리해버림
 
 
 

@ExceptionHandler

  • 보통 실무에서 @ExceptionHandler를 사용하여 예외처리를 진행한다.
  • ExceptionHandlerExceptionResolver에서 제공한다.
  • 사용법
    • @ExceptionHandler 어노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해준다.
      • 지정한 예외 또는 그 예외를 상속하는 자식 클래스는 모두 잡힌다.
    • 우선순위가 존재한다. 더 자세한(지엽적인) 예외가 있다면, 해당 @ExceptionHandler가 호출된다.
      • // 그냥 Exception보다 더 자세한 예외 @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException e) { log.info("handleIllegalArgument", e); ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; return ResponseEntity.status(errorCode.getHttpStatus()) .body(makeErrorResponse(errorCode)); } // 나머지 Exception 처리 @ExceptionHandler({Exception.class}) public ResponseEntity<Object> handleAllException(Exception ex) { log.info("handleAllException", ex); ErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR; return ResponseEntity.status(errorCode.getHttpStatus()) .body(makeErrorResponse(errorCode)); }
 
 

@ControllerAdvice, @RestControllerAdvice

  • @ControllerAdvice 또는 @RestControllerAdvice를 사용하여 정상 코드와 예외처리 코드를 분리할 수 있다.
    • @ControllerAdvice, @RestControllerAdvice의 차이는 @ResponseBody로 Json 응답을 줄지 말지의 차이이다.(@Controller - @RestController의 차이와 같다.)
  • ControllerAdvic는 @ExceptionHandler모든 컨트롤러에 전역적으로 적용해준다.
    • 따라서, 전역적으로 에러를 핸들링하는 클래스를 만들 수 있다.
    • @RestControllerAdvice @Slf4j public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { // 이제 이 ExceptionHandler들은 전역적으로 처리된다. @ExceptionHandler(RestApiException.class) public ResponseEntity<Object> handleCustomException(RestApiException e) { ErrorCode errorCode = e.getErrorCode(); return handleExceptionInternal(errorCode); } @ExceptionHandler(BadRequestException.class) public ResponseEntity<Object> handleCustomException(BadRequestException e) { log.info("handleBadRequest", e); ErrorCode errorCode = CommonErrorCode.BAD_REQUEST; return handleExceptionInternal(errorCode); } }
  • 특정 클래스나 어노테이션이 붙은 컨트롤러들을 지정할 수도 있다.
    • @ControllerAdvice("org.example.controllers") // 해당 클래스에만 동작
 
Share article

Tom의 TIL 정리방