(Spring) 예외 처리(ExceptionHandling)

예외 처리가 필요한 이유는 무엇입니까?

  • 발생한 예외에 따라 다른 HTTP 상태로 응답 값을 보내야 어떤 예외가 발생했는지 명확히 알 수 있습니다.
    그러면 응답 값에 따라 메시지/화면이 처리되어 사용자에게 더 나은 사용자 경험을 제공합니다.
    예외를 처리하는 다양한 방법을 살펴보겠습니다.

HandlerExceptionResolver 사용

  • 예외 해결자 운영 프로세스
    • 신청하기 전에: 요청 -> 디스패처 서블릿 -> 핸들러 어댑터 -> 디스패처 서블릿 -> 응답
    • 신청 후: 요청 -> 디스패처 서블릿 -> 핸들러 어댑터 -> 예외 확인자 -> 응답
@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()은 throw합니다.
    재정의하여 예외 논리를 작성해야 합니다.
    그런 다음 생성된 예외 처리 클래스를 WebMvcConfigurer에서 상속받은 웹 구성과 관련된 WebConfig 클래스에 추가해야 합니다.
    . 당신이 그것을 처리할 수 있다면 exceptionResolver가 없으면 Exption Resolver가 적용되지 않은 것처럼 예외가 Dispatcher 서블릿으로 전달됩니다(Http 상태 500이 반환됨).

@ExceptionHandler 사용 – 실제로 일반적으로 사용됨

  • ResponseStatusExceptionResolver는 예외에 따라 HTTP 상태 코드에 레이블을 지정하는 역할을 합니다.
@Data
@AllArgsConstructor
public class ErrorResult {
    private String code;
    private String message;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
    log.error("(exceptionHanlder) ex", e);
    return new ErrorResult("BAD", e.getMessage());
}
  • 깨끗한 예외 처리를 위해 ErrorResult와 같은 오류 응답 값에 대한 클래스를 만듭니다.
    @ExceptionHandler를 적용한 후 원하는 예외 클래스를 삽입하고 해당 예외 클래스를 인수 값으로 가져옵니다.
    그런 다음 ErrorResult에 인수로 받은 예외 클래스에 메시지와 상태 코드를 넣고 반환합니다.
    @ResponseStatus(HttpStatus.BAD_REQUEST)가 지정되지 않은 경우 예외가 발생하더라도 HTTP 상태 코드는 200(성공)입니다.
    예외 처리는 @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 코드를 제공하면, @응답상태() 사용할 수 없습니다.
@ExceptionHandler(부모예외)
public String parent(부모 예외 e)

@ExceptionHandler(자식예외)
public String child(자식 예외 e)
  • 참고: Spring은 항상 자식 개체보다 우선합니다.
    자식 클래스가 예외를 throw하면 자식 및 부모 예외를 모두 처리합니다.
    그러나 상위 클래스에서 예외가 발생하면 상위 예외만 발생합니다.

@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(글로벌)로 적용됩니다.

졸업 증서

  • 보다 다양한 방법으로 예외를 처리하는 방법을 알고 각 예외의 장단점을 배웠습니다.
    안정적인 서비스를 위해서는 상태 저장 예외 처리도 중요합니다.

참조

  • 김영한의 Infra 강의 (Spring MVC Pattern 2)