서블릿은 2가지 방식으로 예외를 처리한다.
1. Exception(예외)
2. response.sendError(Http 상태 코드, 오류 메세지)
자바의 경우 메인 메서드 실행의 경우 main이라는 이름의 쓰레드가 실행되는데, 예외가 발생하여 이를 잡지 못하면, 예외정보를 남기고 쓰레드는 종료된다.
반면에 웹 어플리케이션의 경우 사용자 요청마다 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다. 그런데 어플리케이션에서 예외가 발생하고, try catch등을 통해 예외를 잡지 못하면 서블릿 바깥으로 예외가 전달된다.
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
#스프링 기본 예외 화면 비활성화
server.error.whitelabel.enabled=false
@Slf4j
@Controller
public class ServletExceptionController {
@GetMapping("/error-ex")
public void errorEx() {
throw new RuntimeException("예외 발생!");
}
}
이제 해당 url로 접근해서 확인해보면 아래처럼 톰캣에서 제공하는 기본 예외 화면을 볼 수 있다.
HttpServletResponse를 이용해 당장 예외를 발생시키는 것이 아니라 서블릿 컨테이너에게 오류가 발생했다는 것을 전달하는 방법이다.
WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError())
@GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
response.sendError(404, "404오류!");
}
@GetMapping("/error-500")
public void error500(HttpServletResponse response) throws IOException {
response.sendError(500, "500오류!");
}
각 url로 접근해보면 아래처럼 예외 화면과 함께 상태 코드가 넘어가는 것을 확인할 수 있다.
그런데 화면을 보면 알겠지만 사용자 입장에서는 뭐가 문제인지도 모르고 불편함을 끼칠 수 있다. 따라서 좀 더 의미있는 화면을 제공해보자.
과거에는 web.xml에 오류 화면을 등록했다.
<web-app>
<!--404에러가 발생했을 때-->
<error-page>
<error-code>404</error-code>
<location>/error-page/404.html</location>
</error-page>
<!--500에러가 발생했을 때-->
<error-page>
<error-code>500</error-code>
<location>/error-page/500.html</location>
</error-page>
<!--RuntimeException이 발생했을 때-->
<error-page>
<exception-type>java.lang.RuntimeException</exception-type>
<location>/error-page/500.html</location>
</error-page>
</web-app>
하지만 지금은 스프링 부트가 제공하는 기능을 이용해 에러 페이지를 등록할 수 있다.
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
// 상태코드 발생 시
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
// 예외 발생 시
// RuntimeException의 자식 예외 또한 같이 처리해준다.
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
@Slf4j
@Controller
public class ErrorPageController {
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 404");
return "error-page/404";
}
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 500");
return "error-page/500";
}
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2>500 오류 화면</h2>
</div>
<div>
<p>오류 화면 입니다.</p>
</div>
<hr class="my-4">
</div> <!-- /container -->
</body>
</html>
에러 발생
1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
에러 페이지 호출
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/errorpage/
500) -> View
//RequestDispatcher에 아래의 값들이 상수로 정의되어 있음
public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
public static final String ERROR_MESSAGE = "javax.servlet.error.message";
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
private void printErrorInfo(HttpServletRequest request) {
// 예외
log.info("ERROR_EXCEPTION: {}", request.getAttribute(ERROR_EXCEPTION));
// 예외 타입
log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
// 에러 메세지
log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE));
// 클라이언트 요청 URI
log.info("ERROR_REQUEST_URI: {}", request.getAttribute(ERROR_REQUEST_URI));
// 에러가 발생한 서블릿 이름
log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(ERROR_SERVLET_NAME));
// HTTP상태 코드
log.info("ERROR_STATUS_CODE: {}", request.getAttribute(ERROR_STATUS_CODE));
log.info("dispatchType={}", request.getDispatcherType());
}
출처 : 김영한 스프링 MVC2편