예외처리 - Spring 에서 기본적인 예외 처리

박영준·2023년 6월 17일
0

Spring

목록 보기
16/58

1. 흐름

기본적인 에러 처리 방식은 에러 컨트롤러를 한 번 더 호출하는 것이다.
그러므로 필터나 인터셉터가 다시 호출될 수 있는데, 이를 제어하기 위해서는 별도의 설정이 필요하다.

2. 구성

(1) WAS

  • 웹서버와 웹 컨테이너의 결합
    (웹 컨테이너 : 클라이언트의 요청이 있을 때, 내부의 프로그램을 통해 결과를 만들어내고, 이것을 다시 클라이언트에 전달)

  • 다양한 기능을 컨테이너에 구현하여 다양한 역할을 수행할 수 있는 서버

  • 동적인 데이터를 처리하는 서버
    (DB와 연결되어 데이터를 주고 받기 or 프로그램으로 데이터 조작이 필요한 경우)

  • tomcat 을 흔히 WAS(Web Application Server)라고 말한다.

(2) 필터

  • 서블릿 기술이므로, 필터 등록(FilterRegistrationBean) 시에 호출될 dispatcherType 타입을 설정 가능

  • 별도의 설정이 없다면, REQUEST일 경우에만 필터가 호출

(3) 서블릿

  • dispatcherType 로 요청의 종류를 구분
    • 일반적인 요청 : REQUEST
    • 에러 처리 요청 : ERROR

(3) 인터셉터

  • Spring 기술이므로, dispatcherType을 설정할 수 없어 URI 패턴으로 처리가 필요

  • 그러나, Spring Boot 에서는 WAS까지 직접 제어하게 되면서,

    • WAS의 에러 설정까지 가능해졌다.
    • 이는 요청이 2번 생기는 것이 아니라, 1번의 요청이 2번 전달되는 것이다.

    → 그러므로 클라이언트는 에러 처리 작업이 진행되었는지 알 수 X

3. BasicErrorController

Spring 은 처음부터 에러 처리를 위한 BasicErrorController 를 구현해두었다.
별도의 설정이 없다면, /error로 에러 요청을 다시 전달하는 WAS의 설정에 의해 BasicErrorController 로 에러처리 요청이 전달된다.

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    private final ErrorProperties errorProperties;
    ...

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ...
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        ...
        return new ResponseEntity<>(body, status);
    }
    ...
}

이러한 기본 설정으로는 다음과 같은 에러 응답을 받게 된다.
이는 클라이언트 입장에서는 어떤 에러가 발생했는지 명확히 파악이 어려울 수 있다.

{
    "timestamp": "2021-12-31T03:35:44.675+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/product/5000"
}

properties를 통해 에러 응답을 작성해준다고 해도, status는 여전히 500이며 유의미한 에러 응답을 전달하지 못한다.

{
    "timestamp": "2021-12-31T03:35:44.675+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "trace": "java.util.NoSuchElementException: No value present ...",
    "message": "No value present",
    "path": "/product/5000"
}

4. 예외 처리 흐름

1) 예외 처리기

  • ExceptionHandlerExceptionResolver: 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리함

  • ResponseStatusExceptionResolver: Http 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException를 처리함

  • DefaultHandlerExceptionResolver: 스프링 내부의 기본 예외들을 처리한다.

각 예외 처리기들은 스프링의 빈으로 등록되어 있고,
예외가 발생하면 순차적으로 다음의 Resolver들이 처리가능한지 판별한 후에 예외가 처리된다.
(2. Spring 의 예외 처리 中 ② Object handler 참고)

2) 흐름

1 ~ 6 : ExceptionHandlerExceptionResolver 가 동작
7 ~ 9 : ResponseStatusExceptionResolver 가 동작
10 ~ 12 : DefaultHandlerExceptionResolver가 동작
12 : 적합한 ExceptionResolver 가 없음

5. 구체적 예외 핸들러 -> 부모 예외 핸들러

Spring 은 예외가 발생하면
가장 구체적인 예외 핸들러를 먼저 찾고, 없으면 부모 예외의 핸들러를 찾는다.

@RestController
@RequiredArgsConstructor
public class ProductController {

    ...

    @ExceptionHandler(NoSuchElementFoundException.class)
    public ResponseEntity<ErrorResponse> handleItemNotFoundException(NoSuchElementFoundException exception) {
        ...
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
        ...
    }
    
	// 부모 예외 핸들러
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAllUncaughtException(Exception exception) {
        ...
    }
}

NullPointerException 이 발생했다고 가정해보자.

위에서는 NullPointerException 처리기가 없으므로, Exception 에 대한 처리기가 찾아진다.

6. 예외 전환

DB 에 접근하기 위한 @Repository 빈들에는 대표적으로 예외 전환 기법이 사용되고 있다.

1) 추상화된 예외로 전환

(H2 DB 에서 발생한 에러가 최종적으로 Spring 에러로 추상화)

추상화 X
Spring 어플리케이션 이용 시, 어떤 DB가 사용될지 모른다.

그런데 만약
DB 를 MySQL에서 PostgreSQL로 전환하는 경우
에러 추상화가 없다면, 모든 MySQL 에러를 PostgreSQL로 전환해야한다.

추상화 O
Spring 의 @Repository 에는 각기 다른 DB 에 의한 에러를 DataAccessException(Spring 의 DB 에러)로 전환해주는 기능이 있다.
→ 이를 통해, DB 에 종속되지 않는 개발이 가능

2) 언체크 예외로 전환

DB 에 의한 에러는 일반적으로 체크 예외이지만,
DB 에 의한 체크 예외를 throws 받아도, 특별히 할 작업은 따로 없고 에러 코드를 내려주는 것 뿐이다.

예시
중복된 ID로 가입을 하는 경우, Constraint 에러가 발생하였다면
우리는 올바른 에러 코드를 내려주면 된다 O
체크 예외로 처리할 필요 X → 만약 체크 예외를 그대로 가져간다면, 불필요하게 throw해주어야 하는 코드만 多

Spring 은 이런 문제를 해결하기 위해 예외를 언체크 예외로 정의
→ 이를 통해, 무분별한 throw를 하지 않아도 된다.

profile
개발자로 거듭나기!

0개의 댓글