Spring MVC (2) - 예외 처리와 오류 페이지
1. 서블릿 예외처리
- 서블릿은 2가지 방식으로 예외 처리를 지원한다. (Exception, response.sendError(HTTP 상태코드, 오류메시지))
1.1 Exception
- 자바의 경우 try-catch를 하지 못하고 main쓰레드까지 예외가 던져지면, 예외 정보 출력 후 쓰레드 종료
- 웹 애플리케이션의 경우 예외를 잡지 못하는 경우 인터셉터 -> 서블릿 -> 필터 -> WAS 까지 전파됨
@GetMapping("/error-ex")
public void errorEx() {
throw new RuntimeException("예외 발생!");
}
- 위와 같이 예외를 발생시키면, 톰캣 기본 오류 화면을 볼 수 있다. (HTTP Status 500 - Internal Server Error)
- Exception의 경우 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 보고 HTTP 상태 코드 500을 반환한다.
1.2 response.sendError(HTTP 상태 코드, 오류 메시지)
- 당장 예외 발생 X
- 서블릿 컨테이너에 오류 발생을 전달할 수 있다.
- 컨트롤러 -> 인터셉터 -> 서블릿 -> 필터 -> WAS (sendError 확인)
- sendError에 전달된 에러를 보고 오류 페이지를 보여줌
2. 서블릿 예외 처리 - 오류 화면 제공
- Exception 혹은 sendError()시 상황에 따라 오류 처리 기능 제공한다.
- 스프링 부트를 통해서 서블릿 오류 페이지를 등록해보자.
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/errorpage/404");
ErrorPage errorPage500 = new
ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/errorpage/500");
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
- 위와 같이 에러 페이지를 등록할 수 있다.
- 오류 페이지는 예외를 다룰 때 해당 예외의 자식 타입의 오류도 함께 처리한다.
2.1 오류 처리 컨트롤러
@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";
}
}
3. 서블릿 예외 처리 - 오류 페이지 작동 원리
- Exception 혹은 sendError()이 호출되면, 설정된 오류 페이지를 찾는다.
- WAS는 예외를 처리하는 오류페이지 정보를 확인한다.
(new ErrorPage(RuntimeException.class, "/error-page/500")
- 내부적으로 해당 url을 다시 요청한다
(필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 (/error-page/500) -> 뷰
* 중요한 점은 클라이언트는 이런 내부 호출을 전혀 모름 (오류 페이지를 찾기 위한 추가 호출)
또한, 내부 재호출시에도 필터, 인터셉터 등 다시 호출 됨을 확인 할 수 있다.
3.1 오류 정보 추가
- WAS는 단순히 재요청 뿐 아니라 request등에 추가 정보를 넘겨준다.
(필요시 사용 가능하다)
- RequestDispatcher 클래스에 상수로 정의되어 있다.
- request의 getDispatcherType은 중요하게 쓰임!
4. 서블릿 예외 처리 - 필터
- WAS의 내부 호출 시 필터,서블릿,인터셉터가 다시 호출 된다.
- 근데 필터나 인터셉터등은 대부분 정상 호출에서 이미 처리됨 -> 재호출은 비효율적
- 따라서 서버는 클라이언트의 정상호출과 에러 페이지 호출을 구분해야함
(DispatcherType을 제공함)
4.1 Dispatcher Type
- 정상호출 시 REQUEST, 에러 페이지 호출 시 ERROR
- > 뿐만 아니라, INCLUDE,ASYNC,FORWARD 등이 있음
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ERROR);
return filterRegistrationBean;
}
- 위와 같이 필터를 등록할 때 setDispatcherTypes를 통해 지정할 수 있다.
5. 서블릿 예외 처리 - 인터셉터
- 인터셉터의 경우 서블릿의 기능이 아니라, 스프링이 제공하는 기능이다.
- 따라서 DispatcherType과는 무관하게 작동한다.
- 다만, 인터셉터가 처리하는 경로는 추가하거나 제외하기 쉽다 (excludePathPatterns를 사용하자)
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns(
"/css/**", "/*.ico"
, "/error", "/error-page/**" //오류 페이지 경로
);
6 스프링 부트 - 오류 페이지
- 스프링 부트는 ErrorPage, ErrorPageController 만드는 작업을 기본으로 제공함
- 이때 ErrorPage 등록 시 /error로 url을 매핑한다.
- 모든 Exception, 혹은 response.sendError을 /error하나로 처리한다.
- 이때 컨트롤러로 BasicErrorController이 사용된다.
ErrorPage에서 등록한 /error을 매핑해서 처리한다.
* BasicErrorController에 기본 로직이 모두 개발되어 있다.
- BasicErrorController가 제공하는 룰과 우선순위에 따라 오류 페이지 등록하자
(정적 HTML - > 정적 리소스 자리, 뷰 템플릿 -> 동적 리소스 자리)
6.1 뷰 선택 우선순위
- 해당 경로 위치에 HTTP 상태 코드 이름의 뷰 파일을 넣어두자
- 5xx, 4xx로 두면, 500 혹은 400대 오류를 모두 처리해준다.
7. 스프링 부트 - 오류 페이지2
- BasicErrorController는 오류 정보를 model에 담아서 전달한다. (message,path, status 등)
- 뷰 템플릿은 이 값을 활용해서 출력할 수 있다.
- 또한 application.properties를 통해 model에 포함할 오류 정보를 선택할 수 있다.
#application.properties
server.error.include-exception=false #: exception 포함 여부( true , false )
server.error.include-message=never #: message 포함 여부 on_param 등과 같이 파라미터 있을 때에만 제공할 수 있다.
server.error.include-stacktrace=never #: trace 포함 여부
server.error.include-binding-errors=never #: errors 포함 여부
server.error.whitelabel.enabled=true # 오류 페이지 못찾으면, whirelabel 오류 페이지 적용
server.error.path=/error # 스프링이 자동 등록하는 글로벌 오류 페이지 경로와 BasicErrorController 경로 함께 사용됨
- on_param을 사용하면, 파라미터 정보가 노출됨 -> 디버그시 유용 (개발 서버에서만 사용하자)
* 에러 공통 처리 컨트롤러를 변경하고 싶으면, ErrorController 인터페이스를 상속 받아서 구현하거나
BasicErrorController를 상속 받아서 기능을 추가하자