[Spring] - API예외 처리를 이해하기위한 기본 개념!
이 포스팅은 이전에 작성한 아래 포스팅을 읽었다는 가정하에 작성한다.이전포스팅을 읽어야만 현재 포스팅이 이해되는 것은 아니지만 글의 흐름을 이전포스팅과 연관지어서 작성하며 코드를
lee-dev-log.tistory.com
[Spring] 쉬우면서 정확하게 익혀보는 필터와 인터셉터의 예외처리 흐름과 예외 페이지 응답
스프링을 사용하지 않는 순수 서블릿 컨테이너는 Exception과 response.sendError(Http상태코드, 오류메시지)두 가지 방식으로 예외를 처리한다. Exception으로 처리하기기본적으로 자바는 예외가 발생하
lee-dev-log.tistory.com
먼저 이 포스팅을 읽기전에는 이전에 작성했던 위의 포스팅을 읽은 후에 읽는 것을 추천한다.
이해하기 수월할 뿐 아니라 이전 포스팅을 읽었다는 가정 하에 포스팅을 하였다.
HandlerExceptionResolver
의 정확한 동작원리를 알아보기전에 예외가 발생했을 때의 DispatcherServlet
의 흐름을 알아보자.
HandlerExceptionResolver
는 줄여서ExceptionResolver
라고도 부른다.- 예외가 발생했다는 것은 내가 구현한 비즈니스 레이어, 퍼시스턴스 레이어 등에서 예외가 발생하여 프레젠테이션계층 (컨트롤러)까지 해당 예외가 올라온 것을 말한다.
ExceptionResolver
가 적용안된 상태에서 예외발생
- 클라이언트에서 서버로 HTTP 요청
- WAS에서 요청을 받아
DispatcherServlet
으로 요청을 보냄 - 리소스 요청경로를 매핑
- 해당 매핑에 맞는 핸들러어댑터(컨트롤러)를 호출
- 호출한 컨트롤러에서 예외발생!
- 컨트롤러에서 발생한예외를 다시
DispatcherServlet
으로 전달 - 예외가 발생했기때문에
postHandler
메서드는 동작하지 않고afterCompletion
메서드 호출 - WAS로 예외를 전달
ExceptionResolver
가 적용된 상태에서 예외발생
- 클라이언트에서 서버로 HTTP요청
DispatcherServlet
에서 요청을 받고 핸들러(컨트롤러)를 매핑- 호출한 컨트롤러에서 예외발생!
- 컨트롤러에서 발생한 예외를 다시
DispatcherServlet
으로 전달 - 발생한 예외를
ExceptionResolver
가 해결 - 예외처리 afterCompletion
메서드 동작- HTML응답
- 최종적으로 WAS는 예외가 없기때문에 정상흐름으로 동작하게 된다!
위 내용을 단순하게 생각하면 ExceptionResolver
는 예외가 발생하면 해당 예외를 WAS를 통해 응답이 나가기전에 때려잡아주는 역할을 수행한다.
이제 HandlerException코드를 난이도 쉬움 버전으로 구현해보고 동작을 이해해보자!
먼저 기존 ApiExceptionController
컨트롤러에 "bad"라는 파라미터가 들어올 경우 예외를 발생시키면서 "잘못된 입력값"이라는 예외 메시지를 담은 예외를 만든다.
참고로 RuntimeException이던 IllegalArgumentException이던 모든 예외는 WAS입장에서는 그저 서버에서 무언가 잘못된 것으로 인식하기때문에 500상태코드가 나가게된다.
개발자 입장에서 IllegalArgumentException
예외는 사용자가 무언가 입력값을 잘못 넣었다고 생각할 수 있기때문에 우리는 500상태코드로 응답되는 IllegalArgumentException을 400BadRequest가 응답되도록 바꿔볼 것이다.
@Slf4j
@Controller
public class ApiExceptionController {
@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("잘못된 입력값");
}
return new MemberDto(id, "hello " + id);
}
HandlerExceptionResolver
를 구현한 MyHandlerExceptionResolver
를 만들어주자.
@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();
}
} catch (IOException e) {
log.error("resolver ex", e);
e.printStackTrace();
}
return null;
}
}
resolverException
메서드를 오버라이드 할 수 있는데,
반환타입을 보면 ModelAndView
를 반환하는 것을 볼 수 있다.
분명 API예외를 처리하기로했는데 뷰를 반환해도 괜찮나? 라는 의문을 가질 수 있지만 그 부분은 뒤에서 설명한다.
오버라이드한 메서드의 파라미터중 Exception
이 있는것을 볼 수 있다.
이 파라미터의 의미는 이런 예외를 처리할거에요
라는 의미로 해석하면된다
바로 코드를 보면 이해할 수 있는데 ex필드가 만약 IllegalArgumentException
일 경우 응답객체의 sendError
를 담는 것을 확인할 수 있다.sendError
에 원하는 변경하고자하는 상태코드를 담고 선택적으로 메시지까지 담을 수 있다.
이렇게 설계하면 이전에 우리가 학습했던대로 WAS가 예외를 인지하면 해당 예외를 처리할 수 있는지 sendError
를 뒤져보고 일치하는 예외 또는 상태코드가 있다면 다시 컨트롤러로 해당 예외를 처리하도록 보내고 해당 예외에 맞는 응답을 보내게 되는 것이다.
그리고나서 빈 ModelAndView
를 리턴해주고있는데
반환된 ModelAndView
의 값에 따라 DispatcherServlet
의 동작방식이 달라진다.
- 빈
ModelAndView
: 뷰를 렌더링하지 않고 정상흐름으로 서블릿이 리턴 ModelAndView
를 지정: 지정한 모델을 담은 뷰를 렌더링한다.null
: 널을 반환하면 다음ExceptionResolver
를 찾아보고 처리할 수 없을 경우 기존에 발생한 예외를 서블릿 밖으로 던진다.
JSON형태로 API응답을 한다면 빈 ModelAndView
를 반환하면된다.
그리고나서 WebConfig
클래스에
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
}
구현한 ExceptionResolver
를 등록해주면 우리가 원하는대로 예외를 처리할 수 있다!
POSTMAN을 이용하여 테스트해보자.
localhost:8080/api/members/bad
이 경로로 GET요청을 보내보자.
응답결과
{
"timestamp": "2024-09-05T13:17:09.874+00:00",
"status": 400,
"error": "Bad Request",
"exception": "java.lang.IllegalArgumentException",
"path": "/api/members/bad"
}
- JSON형태로 응답하며, 상태코드가 400으로 BadReqeust가 반환된 것을 확인할 수 있다.
resolveException
에서 예외를 처리했기때문에 서버로그에도 예외가 남지않는다!
이번에는 null을 리턴해보자!!
똑같이 localhost:8080/api/members/bad
이 경로로 GET요청을 보내보자
실행결과
{
"timestamp": "2024-09-05T13:18:16.976+00:00",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.IllegalArgumentException",
"path": "/api/members/bad"
}
위와 같이 sendError
가 적용되지 않으며 500 Unternal Server Error가 응답된 것을 확인할 수 있다.
또한 서버로그를 확인하면 아래와 같이 예외가 찍혀있는 것을 확인할 수 있다. 즉 예외가 정상적으로 처리되지 않은 것이다.
'Framework > Spring' 카테고리의 다른 글
[Spring] - HandlerExceptionResolver 활용하여 예외처리하기 (2) | 2024.09.07 |
---|---|
[Spring] - API예외 처리를 이해하기위한 기본 개념! (2) | 2024.09.06 |
[Spring] 쉬우면서 정확하게 익혀보는 필터와 인터셉터의 예외처리 흐름과 예외 페이지 응답 (0) | 2024.09.04 |
[Spring] 서블릿 필터와 스프링 인터셉터 비교하기! (0) | 2024.08.07 |
[Spring] JdbcTemplate (0) | 2024.06.16 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!