Spring Framework의 예외처리 방법을 정리하기에 앞서 자바와 서블릿의 예외처리 매커니즘을 이해하여야 한다.
자바 프로그램 중 발생한 Exception은 catch문으로 적절한 처리를 하지 않으면 메소드를 호출한 상위 call stack으로 전파되며, main에서도 예외가 처리되지 않으면 쓰레드가 종료된다. 관련글
자바 웹 애플리케이션의 경우 WAS는 사용자 요청마다 쓰레드를 생성한다. 만약 각 사용자 요청(쓰레드) 처리 중 예외가 발생하고 중간에 적절히 처리되지 않으면 예외는 WAS까지 전파된다. WAS는 처리되지 않은 Exception을 Http상태코드와 함께 사용자에게 응답한다.(웹 애플리케이션은 중단되지 않는다.)
자바 웹 애플리케이션은 예외를 발생시키지 않고도 HttpServlet
객체의 sendError()
를 이용해서 WAS에 에러정보(Http 상태코드, 메시지)를 전달할 수도 있다.
기존에는 web.xml 등을 이용해 에러/예외에 대한 응답을 처리했다.
Spring을 이용할 경우 아래와 같은 방법을 이용해 에러코드에 대한 예외를 WAS에 등록할 수 있다.
@Component
public class WebServerCustomizer implements WebServerCustomizer<ConfigurableWebServerFactory>{
@Override
public void customize(ConfigurableWebserverFactory factory){
//에러 상태코드에 대한 에러 처리 path등록
ErrorPage page404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
//예외 발생(자식 포함)에 대한 에러 처리 path등록
ErrorPage pageEx = new ErrorPage(EntityNotFoundException, "/error/404");
//에러 페이지 등록
factory.addErrorPages(page404, pageEx);
}
}
ErrorMvcAutoConfiguration
에 구현해 두었다. 기본 설정을 이용할 경우 에러가 발생하면 /error를 호출하게 되며, 해당 path에는 이미 BasicErrorCtonroller
가 매핑되어 있다.WebserverCustomizer
는 사용하면 안 된다.public class webConfig implements WebMvcConfigurer{
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolvers> resolvers){
resolvers.add(new CustomHandlerExceptionResolver);
resolvers.add(new CustomApiHandlerExceptionResolver);
}
}
public class CustomHandlerExceptionResolver implements HandlerExceptionResolver{
@Oveerride
public ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex){
try{
if(ex instanceof EntityNotFoundException){
response.sendError(HttpServeltResponse.SC_BAD_NOTFOUND, message);
//View를 지정하지 않았으므로 WAS에서 기본 404에 해당하는 에러페이지로 요청을 보낸다.(테스트 필요, sendError하면서 view를 지정하면?)
//View를 지정하여 view를 랜더링해 응답할 수도 있다.
return new ModelAndView();
}
}catch(IOException e){
}
//null인 경우 다음 resolver를 찾고 최종적으로 처리가 안 된 경우에는 WAS까지 Exception을 전파한다.
return null;
}
}
public class CustomApiHandlerExceptionResolver implements HandlerExceptionResolver{
ObjectMapper objectMapper = new ObjectMapper();
@Oveerride
public ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex){
try{
if(ex instanceof EntityNotFoundException){
String header = request.getHeader("accept");
response.sendError(HttpServeltResponse.SC_BAD_NOTFOUND, message);
if(MediaType.APPLICATION_JSON_VALUE.equals(header){
Map<String, Object> errorResult = new HashMap<>();
errorResut.put("ex", ex.getClass());
String result = objectMapper.writeValueAsString(errorResult);
response.setContentType();
response.setCharacterEncoding("utf-8");
response.getWriter().write(result);
}
}
}catch(IOException e){
}
//null인 경우 다음 resolver를 찾고 최종적으로 처리가 안 된 경우에는 WAS까지 Exception을 전파한다.
return null;
}
}
ExceptionHandlerrExceptionResolver
2. ResponseStatusExceptionResolver
3. DefaultHandlerExceptionResolver
를 제공하며, 순서대로 적용된다.ResponseStatusExceptionResolver
은 @ResponseStatus가 붙은 Exception또는 ResponseStatusException
을 처리해주는 resolver이다.DefaultHandlerExceptionResolver
은 Spring이 내부에서 발생한 Exception을 적절한 상태코드로 변경하기 위한 resolver이다.ExceptionHandlerrExceptionResolver
를 이용한 예외처리 방법이다.@RestController
public controllerex{
//상태 지정 안 해주면 200으로 응답해줌
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHanler(EntityNotFoundException.class)
public ErrorResult EntityNotFoundExHandler(EntityNotFoundException e){
return enw ErrorResult("Not Found", e.getmessage());
}
@GetMapping("/members/{id}")
public MemberResponse getMember(@PathValirable Long id){
}
}
@RestContollerAdvice
public class GlobalAdvice{
@ExceptionHanler(EntityNotFoundException.class)
public ErrorResult EntityNotFoundExHandler(EntityNotFoundException e){
return enw ErrorResult("Not Found", e.getmessage());
}
}