minOS

API 예외 처리 - HandlerExceptionResolver 시작 본문

TIL/김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술

API 예외 처리 - HandlerExceptionResolver 시작

minOE 2024. 10. 7. 14:07
728x90

API 예외 처리 - HandlerExceptionResolver 시작

목표

예외가 발생해서 서블릿을 넘어 WAS까지 예외가 전달되면 HTTP 상태코드가 500으로 처리된다. 발생하는 예외에 따라서 400, 404 등등 다른 상태코드로 처리하고 싶다.   오류 메시지, 형식등을 API마다 다르게 처리하고 싶다.

- 가정
`IllegalArgumentException` 을 처리하지 못해서 컨트롤러 밖으로 넘어가는 일이 발생하면 HTTP상태코드를 400으로 처리하고 싶다. 어떻게 해야할까?


@Slf4j
@RestController
public class ApiExceptionController {
    @GetMapping("/api/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id){
        if (id.equals("ex")){
            throw new RuntimeException("잘못된 사용자");
            //500 html 반환 문제를 해결하려면 오류 페이지 컨트롤러도 JSON응답 할 수 있도록 수정해야한다.
        }

        if(id.equals("bad")){
            throw new IllegalArgumentException("잘못된 입력 값");
        }

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

    @Data
    @AllArgsConstructor
    static class MemberDto{
        private String memberId;
        private String name;
    }

}​


http://localhost:8080/api/members/bad 라고 호출하면 `IllegalArgumentException` 이 발생하도록 했다.
실행해보면 상태 코드가 500인 것을 확인할 수 있다.
{
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.IllegalArgumentException",
"path": "/api/members/bad"
}​



HandlerExceptionResolver

스프링 MVC 컨트롤러(핸들러) 밖으로 예외가 던져진 경우 예외를 해결하고, 동작을 새로 정의할 있는 방법을

공한다. 컨트롤러 밖으로 던져진 예외를 해결하고, 동작 방식을 변경하고 싶으면 `HandlerExceptionResolver`

사용하면 된다. 줄여서 `ExceptionResolver` 한다.


1~n 숫자 따라가면서 이해하기 

HandlerExceptionResolver - 인터페이스

public interface HandlerExceptionResolver {
	ModelAndView resolveException(
	HttpServletRequest request, HttpServletResponse response,
	Object handler, Exception ex);
}

- `handler` : 핸들러(컨트롤러) 정보

- `Exception ex` : 핸들러(컨트롤러)에서 발생한 발생한 예외


package hello.exception.resolver;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import java.io.IOException;

@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

        try {

            if (ex instanceof IllegalArgumentException) { //IllegalArgumentException이 발생하면 400 오류로 바꿈
                log.info("IllegalArgumentException resolver to 400");
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
                return new ModelAndView();

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

        return null;
    }
}​

여기서는 `IllegalArgumentException` 이 발생하면 `response.sendError(400)` 를 호출해서 HTTP 상태
코드를 400으로 지정하고, 빈 `ModelAndView` 를 반환한다.


반환 값에 따른 동작 방식

ModelAndView: `new ModelAndView()` 처럼 `ModelAndView` 반환하면 뷰를 렌더링 하지 않고,

정상 흐름으로 서블릿이 리턴된다.

ModelAndView 지정: `ModelAndView` `View` , `Model` 등의 정보를 지정해서 반환하면 뷰를 렌더링 한다.

null: `null` 반환하면, 다음 `ExceptionResolver` 찾아서 실행한다. 만약 처리할 있는 ExceptionResolver 없으면 예외 처리가 안되고, 기존에 발생한 예외를 서블릿 밖으로 던진다.

 

 

WebConfig에  MyHandlerExceptionResolver 등록
@Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new MyHandlerExceptionResolver()); // 핸들러 등록
    }​




ExceptionResolver 활용


1)예외 상태 코드 변환

예외를 `response.sendError(xxx)` 호출로 변경해서 서블릿에서 상태 코드에 따른 오류를 처리하도록 위임 이후 WAS 서블릿 오류 페이지를 찾아서 내부 호출, 예를 들어서 스프링 부트가 기본으로 설정한 `/error` 호출됨


2)뷰 템플릿 처리

`ModelAndView` 값을 채워서 예외에 따른 새로운 오류 화면 렌더링 해서 고객에게 제공


3)API 응답 처리

`response.getWriter().println("hello");` 처럼 HTTP 응답 바디에 직접 데이터를 넣어주는 것도 가능하다. 여기에 JSON 으로 응답하면 API 응답 처리를 있다.



http://localhost:8080/api/members/ex 실행


http://localhost:8080/api/members/bad 실행

 

728x90