API 예외 처리 - ExceptionResolver 1

Drumj·2022년 11월 29일
0

오늘도 스프링이 제공해주는걸 받아먹자

먼저 스프링이 제공해주는걸 받아먹기 전에
HandlerExceptionResolver를 사용해 봐따.

@Slf4j
public class UserHandlerExceptionResolver implements HandlerExceptionResolver {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof UserExceptione) {
                log.info("UserException resolver to 400");
                String acceptHeader = request.getHeader("accept");
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);

                if ("application/json".equals(acceptHeader)) {
                    Map<String, Object> errorResult = new HashMap<>();
                    errorResult.put("ex", ex.getClass());
                    errorResult.put("message", ex.getMessage());

                    String result = objectMapper.writeValueAsString(errorResult);

                    response.setContentType("application/json");
                    response.setCharacterEncoding("utf-8");
                    response.getWriter().write(result);

                    return new ModelAndView();

                } else {
                    // TEXT/HTML
                    return new ModelAndView("error/500");
                }
            }

        } catch (IOException e) {
            log.error("resolver ex", e);
        }
        return null;
    }
}

아따... 길다 길어...

@GetMapping("/api/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id) {

        if (id.equals("ex")) {
            throw new RuntimeException("잘못된 사용자");
        }
        if (id.equals("bad")) {
            throw new IllegalArgumentException("잘못된 입력 값");
        }
        if (id.equals("user-ex")) {
            throw new UserExceptione("사용자 오류");
        }

        return new MemberDto(id, "hello " + id);
    }

이건 컨트롤러!

여기 throw new UserException으로 들어온 거를 resolverException에서 400으로 바꿔주고 또 뭐 어쩌고 저쩌고 해가지고 처리....

이렇게 되면 컨트롤러에서 예외가 발생해도 서블릿 컨테이너까지 예외가 전달되지 않고 ExceptionResolver에서 예외를 처리한다고 한다. 즉 스프링 MVC에서 예외 처리가 끝난다는 것. 결과적으로 WAS 입장에서는 정상 처리된 것이다.

하... 근데 이렇게 구현하니까 너무 복잡해!! 그러니까 스프링에서 제공하는걸 받아먹어보자!


스프링이 먹여주는 ExceptionResolver 3형제

스프링부트가 기본 제공하는 ExceptionResolver는 아래와 같이 3가지가 있다고 한다.

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver (-> 우선 순위가 가장 낮다)

ExceptionHandlerExceptionResolver

  • @ExceptionHandler 을 처리. API 예외 처리는 대부분 이 기능으로 해결한다고 한다.

ResponseStatusExceptionResolver

  • HTTP 상태 코드를 지정해줌.

DefaultHandlerExceptionResolver

  • 스프링 내부 기본 예외를 처리.

ResponseStatusExceptionResolver

자 우선 둘째 녀석부터 알아보자. (강의 순서가 그러니까 나도)

이 녀석은 예외에 따라서 HTTP 상태 코드를 지정해주는 역할을 한다고 한다.

  • @ResponseStatus 가 달려있는 예외
  • ResponseStatusException 예외

이 두가지 경우를 처리한다.

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {

}

이렇게 애노테이션이 달려있을때 code 로 Http 상태 코드를 지정, reason으로 메세지를 지정한다.

! 여기서 잠깐 !
원래 메세지는 직접 입력했었지만... error.bad 처럼 코드로 남기면
messages.properties에서 해당 코드를 찾아와 메시지를 남긴다고 한다! 여기서 중요한것은
메세지(message)가 아니라 메세지스~(messages) 라고 강조하심 ㅎㅎㅎㅎ

@GetMapping("/api/response-status-ex1")
    public  String responseStatusEx1() {
        throw new BadRequestException();
    }

그리고 이렇게 컨트롤러에서 BadRequestException()을 불러주면..!

status가 400, message도 잘 담겨있는걸 확인할 수 있다.


그 다음으로 둘째의 두번째 방법인 ResponseStatusException 처리

@GetMapping("/api/response-status-ex2")
public String responseStatusEx2() {
 throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new
IllegalArgumentException());
}

바로 그냥 컨트롤러에서 이렇게 처리해버리면 된다...!

여기서 http 상태 코드를 NOT_FOUND 로 보냈으니까 404로 들어오는 것을 확인 할 수 있다!!!


DefaultHandlerExceptionResolver

이번에 알아볼 녀석은 막내. 이 녀석은 스프링 내부에서 발생하는 스프링 예외를 해결 해준다.
강의에서 공부한 것은 TypeMismatchException이 발생한 경우인데
이 예외를 그냥 두게되면 서블릿 컨테이너까지 오류가 올라가서 결과적으로 500 에러가 발생한다고 한다.
근데 파라미터 바인딩은 대부분 클라이언트가 요청 정보를 잘못 호출해서 발생하는 문제...
그래서 DefaultHandlerExceptionResolver는 이것을 500 -> 400으로 변경시켜준다!

@GetMapping("/api/default-handler-ex")
    public String defaultException(@RequestParam Integer data) {
        return "ok";
    }

자 일단 컨트롤러를 만들고 data는 Integer로 설정.


숫자를 넣었을때는 ok가 나오는 것을 확인하고


숫자가 아닌 DrumJ를 넣었을 때는 400 에러가 나오는 걸 확인 할 수 있다.
exception도 보면 "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException" 어쩌고 저쩌고 타입 미스매치. 스프링 내부에서 터진 예외를 호로록 400으로 바꿔서 보여주는 것이다.

이거는 내부 예외를 바꿔주는 거라 스프링에서 DefaultHandlerExceptionResolver를 따라 들어가면 더 많은 코드를 확인할 수 있다!!!


끝나지 않은 예외 처리....

마지막 남은 대망의 ExceptionHandlerExceptionResolver

@ExceptionHandler 라는 혁신적인 예외 처리 기능을 제공한다고 하는데....

글이 길어지는건 싫으니 다음 게시글에서 (이미 충분히 길다)

총총....

0개의 댓글