[스프링] 스프링에서 예외 처리

June·2021년 8월 11일
0

스프링에서 예외 발생위치

예외 처리 과정

WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

스프링의 처리과정을 보면 예외가 발생하는 부분은 크게 두가지로 나눌 수 있다.

  1. Dispatcher Servlet내에서 발생하는 예외 (Controller, Service, Repository등등)
  2. Dispatcher Servlet전의 서블릿 (Filter)에서 발생하는 예외

DispatcherServlet 예외

스프링 MVC에서 에러의 대다수는 DispatcherServlet에서 발생한다. HandlerExceptionResolver를 사용하면 예외를 내부 (Servlet Container 내부)에서 자체적으로 해결할 수 있다.

https://jaehun2841.github.io/2018/08/30/2018-08-25-spring-mvc-handle-exception/#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0

Web Application 레벨 예외

클라이언트의 요청을 DispatcherServlet 밖에서 처리하는 도중 예외가 발생하면 DispatcherServlet이 예외를 처리해줄 수 없다. 즉, HandlerExceptionResolver의 처리를 받을 수 없다.

처리 못하는 이유는 DispatcherServlet에서 처리하기도 전에 예외가 발생되기 때문이다.

이렇게 Filter에서 예외가 발생하면 Web Application 레벨에서 처리를 해줘야 한다.

Filter에서 예외가 발생한다면?

  1. web.xml에 error-page를 잘 등록해줘서 에러를 사용자에게 응답

  2. Filter내부에서 예외를 처리하기 위한 필터를 따로 둬서 try-catch문을 사용하여 예외 처리

    • 실제 시큐리티 인가 처리중 예외가 발생하면 ExceptionTranslationFilter에게 예외를 던져 처리한다. (try-catch)
  3. Filter 내부에서 try-catch 구문을 통해 예외 발생 시, HandlerExceptionResolver를 빈으로 주입받아 @ExceptionHandler에서 처리하는 방법

    • 즉, 필터에서 발생하는 예외를 DispatcherServlet의 예외 처리기인 HandlerExceptionResolver에 보내서 처리하는 방식이다.

예외 처리 방법

1. @ExceptionHandler

@ExceptionHandler애노테이션을 통해 Controller의 메서드에서 throw된 Exception에 대한 공통적인 처리를 할 수 있다.

@RestController
public class TestController {

    private final Logger logger = LoggerFactory.getLogger(UserController.class);

    // 예외 핸들러
    @ExceptionHandler(value = TestException.class)
    public String controllerExceptionHandler(Exception e) {
        logger.error(e.getMessage());
        return "/error/404";
    }

    @GetMapping("hello1")
    public String hello1() {
        throw new TestException("hello1 에러 "); // 강제로 예외 발생
    }

    @GetMapping("hello2")
    public String hello2() {
        throw new TestException("hello2 에러 "); // 강제로 예외 발생
    }
}

TestController내에서 발생하는 TestException에 대해서 예외가 발생하면 controllerExceptionHandler메서드에서 모두 처리해준다.

  • Controller 메서드 내의 하위 서비스 (Service, Repository등등)에서 예외가 발생하더라도, 중간에 처리하지 않는 이상 Controller단까지 예외가 던져지게 되고 @ExceptionHandler가 예외를 처리하게 된다.
    Checked Exception, Runtime Exception 상관 없이 Controller까지 예외를 throw하면 처리가 가능하다.

2. @ControllerAdvice, @RestControllerAdvice

  • @ControllerAdvice

    • 모든 Controller에서 발생하는 예외를 처리할 수 있게 해주는 애노테이션
    • DispatcherServlet에서 발생하는 예외를 전역적으로 처리해준다.
  • @RestControllerAdvice

    • @ControllerAdvice + @ResponseBody

@ControllerAdvice는 DispatcherServlet에서 발생하는 예외만 처리할 수 있다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = TestException.class)
    public String testExceptionHandler(Exception e) {
        logger.error(e.getMessage());
        return "/error/404";
    }
}
  • Controller에서 발생하는 예외를 전역적으로 처리해준다.

Controller의 @ExceptionHandler와 ControllerAdvice의 @ExceptionHandler중 높은 우선순위는?

  • Controller의 @ExceptionHandler가 먼저다. 항상 스프링에서는 자세할 수록 높은 우선 순위를 가진다.

3. try-catch

@GetMapping("/api/v1/members")
public ResponseEntity getAllMember(Pageable pageable) {

    Page<Member> memberPage;
    
    // 반복적으로 발생하는 Try-Catch
    try {
          memberPage = memberFindService.getAllMemberPage(pageable);
    } catch (RuntimeException re) {
        return ResponseEntity.badRequest().build();
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }

    return ResponseEntity.ok().body(MemberAdminDto.Info.of(memberPage));
}

이런 식으로 할 수 있지만, 각 컨트롤러마다 try-catch를 써주면 양이 엄청나게 늘어날 것이다. 또한 성공 로직과 실패로직이 뒤섞여 있어 가독성이 매우 떨어진다.

try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. - 클린코드 -

참고 블로그: https://github.com/binghe819/TIL/blob/master/Spring/%EA%B8%B0%ED%83%80/%EC%8A%A4%ED%94%84%EB%A7%81%20%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%20%EA%B0%9C%EB%85%90%20%EB%B0%8F%20%EC%A0%84%EB%9E%B5.md

https://jaehun2841.github.io/2018/08/30/2018-08-25-spring-mvc-handle-exception/#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0

https://velog.io/@aidenshin/Spring-Boot-Exception-Controller

0개의 댓글