[스프링 MVC - 2편] 예외 처리와 오류 페이지

지현·2022년 1월 5일
2

스프링

목록 보기
29/32

서블릿 예외 처리 - 시작

서블릿의 예외 처리 지원 방법

  • Exception (예외)
  • response.sendError (HTTP 상태 코드, 오류 메시지)

Exception(예외)

WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
  • Exception이 터졌을 때 기본적으로 WAS는 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 생각해서 HTTP 상태 코드 500을 반환
  • 상태코드나 오류 메세지를 지정해서 넣을 수 없음

response.sendError(HTTP 상태 코드, 오류 메시지)

  • 이것을 호출한다고 당장 예외가 발생하는 것은 아니지만, 서블릿 컨테이너에게 오류가 발생했다는 점을 전달
  • 메서드를 사용하면 HTTP 상태 코드와 오류 메시지도 지정할 수 있음
    • response.sendError(HTTP 상태 코드)
    • response.sendError(HTTP 상태 코드, 오류 메시지)
    • 내가 직접 Http 상태코드나 오류메세지를 담아서 처리하고 싶을 때 사용
WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (response.sendError())
  1. response.sendError()를 호출하면 response 내부에는 오류가 발생했다는 상태를 저장해둠
  2. 서블릿 컨테이너는 고객에게 응답 전에 response에 sendError()가 호출되었는지 확인
  3. 호출되었다면 설정한 오류 코드에 맞추어 기본 오류 페이지를 보여줌

서블릿 예외 처리 - 오류 화면 제공

서블릿은 Exception(예외)가 발생해서 서블릿 밖으로 전달되거나 또는 response.sendError()가 호출 되었을 때 각각의 상황에 맞춘 오류 처리 기능을 제공

서블릿 오류 페이지 등록

@Component
public class WebServerCustomizer 
	implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    //이렇게 하라고 지정이 되어있기 때문에 그대로 사용
    //기본 오류페이지를 커스텀 해서 사용하기 위해 사용
    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        ErrorPage errorPage404 = 
        	new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
        //404에러가 나면 /error-page/400 컨트롤러 호출

        ErrorPage errorPage500 = 
        	new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");

        ErrorPage errorPageEx = 
        	new ErrorPage(RuntimeException.class, "/error-page/500");
        //RuntimeException이 발생하면

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
        //등록
    }
}
  • 오류 페이지는 예외를 다룰 때 해당 예외와 그 자식 타입의 오류를 함께 처리
  • response.sendError(404) : errorPage404 호출
  • response.sendError(500) : errorPage500 호출
  • RuntimeException 또는 그 자식 타입의 예외 : errorPageEx 호출

서버 내부에서 오류가 발생하고 WAS까지 도달하면 WAS에서 다시 해당 오류에 등록된 error 경로를 호출 > 오류 페이지를 처리할 컨트롤러가 필요함

컨트롤러

    //오류 발생 시 오류 화면을 보여주기 위한 컨트롤러

    @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";
    }

서블릿 예외 처리 - 오류 페이지 작동 원리

  1. 예외나 sendError가 발생해서 WAS까지 전달
  2. WAS는 해당 예외를 처리하는 오류 페이지 정보를 확인
  3. WAS는 오류 페이지를 출력하기위해 처음부터 다시 요청, 오류 페이지 경로로 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출됨
    RuntimeException의 오류 페이지로 /error-page/500 이 지정되어 있는 경우 WAS는 오류 페이지를 출력하기 위해 /error-page/500 를 다시 요청
1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 
   컨트롤러(/errorpage/500) -> View
  • 웹 브라우저(클라이언트)는 서버 내부에서 이런 일이 일어나는지 전혀 모름
  • 서버 내부에서 오류 페이지를 찾기 위해 추가적인 호출을 하는 것

오류 정보 추가

  • WAS는 오류 페이지를 단순히 다시 요청만 하는 것이 아니라, 오류 정보를 request의 attribute에 추가해서 넘겨줌
  • 필요하면 오류 페이지에서 전달된 오류 정보를 사용할 수 있음

서블릿 예외 처리 - 필터

  • 오류가 발생하면 오류 페이지를 출력하기 위해 WAS 내부에서 다시 한번 호출이 발생
  • 서버 내부에서 오류 페이지를 호출한다고 해서 해당 필터나 인터셉트가 한번 더 호출되는 것은 매우 비효율적
  • 클라이언트로 부터 발생한 정상 요청인지, 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분해야함 > 서블릿은 DispatcherType이라는 추가 정보 제공

DispatcherType

public enum DispatcherType {
 FORWARD,
 INCLUDE,
 REQUEST,
 ASYNC,
 ERROR
}
  • 클라이언트 요청이면 dispatcherType=REQUEST 출력
  • 오류 요청이면 dispatchType=ERROR 출력
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter(){
        FilterRegistrationBean<Filter> filterRegistrationBean=
        				new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setDispatcherTypes(
        		DispatcherType.REQUEST,DispatcherType.ERROR);
        //이 필터는 REQUEST, ERROR 두가지의 경우에 호출이 된다
        return filterRegistrationBean;

    }
}
  • filter를 등록할때 setDispatcherTypes으로 원하는 Dispatcher 타입에만 해당 필터를 적용하게 할 수 있음
  • 아무것도 넣지 않으면 기본값이 DispatcherType.REQUEST라서 따로 설정하지 않아도 클라이언트의 요청이 있는 경우에만 필터가 적용됨

서블릿 예외 처리 - 인터셉터

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**","*.ico","/error","/error-page/**");
                //오류 페이지 경로
        //인터셉터는 필터처럼 setDispatcherTypes는 없지만 excludePathPatterns를 활용
    }
}
  • 인터셉터에서는 setDispatcherType이 적용되지 않음
  • 대신 excludePathPatterns를 사용해서 오류 페이지 경로를 빼줌

전체 흐름

정상요청일 때

WAS(/hello, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 -> View

오류 요청일 때

1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 
컨트롤러(/error-page/500) -> View
  • 필터는 DispatchType 으로 중복 호출 제거
    setDispatcherTypes(dispatchType=REQUEST)
  • 인터셉터는 경로 정보로 중복 호출 제거
    excludePathPatterns("/error-page/**")

스프링 부트 - 오류 페이지

  • 스프링 부트는 WebServerCustomizer를 만들고, 예외 종류에 따라서 ErrorPage를 추가하고, 예외 처리용 컨트롤러를 만드는 과정을 모두 기본으로 제공
  • 스프링 부트는 기본 오류 페이지를 /error 경로로 설정하여 ErrorPage를 자동 등록 new ErrorPage("/error")
    • 상태코드와 예외를 설정하지 않으면 기본 오류 페이지로 사용됨
    • 서블릿 밖으로 예외가 발생하거나, sendError가 호출되면 모든 오류는 /error를 호출
  • 오류 페이지를 매핑해서 처리하는 컨트롤러(BasicErrorController)를 자동으로 등록하여 ErrorPage에서 등록한 /error를 매핑해서 처리

  • BasicErrorController 는 기본적인 로직이 모두 개발되어 있음
  • 개발자는 오류 페이지 화면만 BasicErrorController가 제공하는 룰과 우선순위에 따라서 등록하면 됨

뷰 선택 우선순위
1. 뷰 템플릿
resources/templates/error/500.html
resources/templates/error/5xx.html
2. 정적 리소스( static , public )
resources/static/error/400.html
resources/static/error/404.html
resources/static/error/4xx.html
3. 적용 대상이 없을 때 뷰 이름( error )
resources/templates/error.html

파일 이름

  • 4xx.html > 400대 에러들에 대한 오류 페이지
  • 404.html > 404 에러에 대한 오류 페이지
  • 자세한게 더 높은 우선순위

정리
오류 페이지를 사용하고 싶으면 상태코드에 따라서 해당 경로 위치에 html 파일만 만들어주면 됨, 나머지는 스프링부트가 다 해줌



출처
[인프런] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

0개의 댓글