![[Spring] ExceptionHandler](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3MTyt%2FbtslCkcPaFz%2FVYj1Px6wPFoyFc6wsl1YM1%2Fimg.png)
클라이언트-서버 간의 통신
클라이언트와 서버 간에 통신에는 HTML을 통한 통신도 있지만 API를 통한 통신도 존재한다.
스프링에서 API 통신을 할 때 예외가 서블릿에서 별도로 처리되지 않으면 WAS로 예외를 넘겨주면서 500 에러를 발생시킨다. 여기서 문제점은 WAS로 넘어간 예외는 항상 500 에러를 발생시키기 때문에 각 상황에 맞는 예외처리가 되지 않는다. (404 에러를 발생시키고 싶은데 500 에러로 처리됨 등)
위 그림을 보면 핸들러에서 발생한 예외가 서블릿(스프링에서는 디스패처 서블릿)으로 넘어가게 되는데 서블릿에서 해당 예외를 별도로 처리하는 로직이 존재하지 않는다면 WAS로 예외를 넘겨버린다. WAS로 넘어간 예외는 말했듯이 500 에러를 발생시킨다.
API 마다 다르게 예외 처리
위의 문제를 해결하기 위해서 스프링에서는 HandlerExceptionResolver 인터페이스를 제공한다.
위 그림을 보면 핸들러에서 발생한 예외가 서블릿으로 전달되는 것까지는 동일하다. 그러나 HandlerExceptionResolver를 적용하면 서블릿에서 해당 예외를 별도로 처리하는 로직을 실행하기 때문에 API 마다 다른 예외를 발생시킬 수 있다.
서블릿은 WAS로 예외를 넘기지 않고 HandlerExceptionResolver로부터 해당 예외를 처리할 수 있는 로직이 있는지 확인을 한다. 로직을 찾아 실행하였다면 항상 500 에러가 발생하던 것을 내가 원하는 에러로 변경하여 클라이언트에게 응답해준다.
아래의 상황은 다음과 같다.
- 클라이언트가 "/api/members/bad"로 서버에게 요청
- "bad"가 파라미터로 넘어왔으므로 IllegalArgumentException 발생
- 서블릿에서 HandlerExcpetionResolver 호출
- HandlerExceptionResolver에서 해당 예외를 처리하는 로직을 검색
- 존재한다면 빈 값의 ModelAndView 반환 -> 클라이언트에게 정상적으로 응답
- 존재하지 않는다면 WAS에게 예외를 넘겨 500 에러 발생
Controller
@Slf4j
@RestController
public class ApiExceptionController {
@GetMapping("/api/members/{id}")
public MemberDto getMember(@PathVariable("id") String id) {
if("ex".equals(id)) {
throw new RuntimeException("잘못된 사용자");
}
if("bad".equals(id)) {
throw new IllegalArgumentException("잘못된 입력 값");
}
return new MemberDto(id, "hello " + id);
}
@Data
@AllArgsConstructor
static class MemberDto {
private String memberId;
private String name;
}
}
HandlerExceptionResolver
@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(400, ex.getMessage());
return new ModelAndView();
}
} catch(IOException e) {
log.error("resolver ex", e);
}
return null;
}
}
결론
서블릿에서 API마다 별도로 처리하는 로직이 없다면 WAS로 예외를 넘기면서 500 에러 발생시킨다.
API 마다 다르게 처리하고 싶다면 HandlerExceptionResolver를 적절히 사용하여 서블릿에서 처리할 수 있도록 한다.