Spring 예외 처리

hoyong.eom·2023년 7월 26일
0

스프링

목록 보기
22/59
post-thumbnail

Spring

서블릿 예외 처리

서블릿에서는 2가지 방식으로 예외를 처리한다고 한다.

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

Exception

웹 애플리케이션은 사용자 요청 별로 별도의 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다.
만약 try-catch로 예외를 잡는다면 문제 없지만, 예외를 잡지 못하면 아래와 같이 예외가 전달된다.

WAS(tomcat) <-- 필터 <-- 서블릿(디스패처 서블릿) <-- 인터셉터 <-- 컨트롤러(예외 발생)

일반적으로 웹 애플리케이션은 예외가 발생하면 Http 상태코드 500을 반환한다고 한다.

response.sendError

오류가 발생했을때, HttpServletResponse가 제공하는 sendError라는 메서드를 사용해도 된다.
sendError를 호출한다면 예외는 아니지만, 서블릿 컨테이너에게 오류가 발생했다고 알릴 수 있다.
sendError 메서드도 Exception이 발생했을때 와의 흐름이 동일하다.

WAS(Tomcat에서 sendError 호출 기록 확인) <-- 필터 <-- 서블릿(디스패처 서블릿) <-- 인터셉터 <-- 컨트롤러(sendError 호출)

sendError 메서드를 호출하면 response 에 sendError()가 호출되었는지 그 상태를 저장해두고 서블릿 컨테이너에서는 sendError 호출 기록을 확인한다고 한다.


서블릿 오류 화면

서블릿은 Exception(예외) 또는 sendError() 메서드가 호출되었을떄 각각의 상황에 맞춘 오류 처리를 가능하도록 제공한다. 스프링부트를 적용하기 전에는 web.xml에 error 페이지를 등록했다고 한다.

<web-app>
 <error-page>
 <error-code>404</error-code>
 <location>/error-page/404.html</location>
 </error-page>
 <error-page>
 <error-code>500</error-code>
 <location>/error-page/500.html</location>
 </error-page>
 <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");

        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
    }
}

WebServerFactoryCustomizer를 구현하는 클래스를 만들어 customize 함수를 오버라이드한다.(스프링에서 정해놓은 규격이므로 암기밖에..)

ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");

위 코드를 예시로 간단히 분석해보자면 404에러(HttpStatus.NOT_FOUND)가 발생하면 /error-page/404.html을 호출해주도록 지정해놓는다.

이렇게 생성된 페이지들을 factory에 등록해주면 된다.

ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");

위 코드에서 RuntimeException.class 또는 그 자식타입의 예외까지 모두 포함한다.


오류 페이지 작동 원리

Exception이 발생해서 서블릿 밖으로 전달되거나 response.sendError()가 호출되었을때는 설정된 오류 페이지를 찾는다.

예외 발생 흐름

WAS <-- 필터 <-- 서블릿 <-- 인터셉터 <-- 컨트롤러(예외 발생)

sendError 흐름

WAS(sendError 호출 기록 확인) <-- 필터 <-- 서블릿 <-- 인터셉터 <-- 컨트롤러(response.sendError)

WAS에서는 등록된 오류 페이지 정보를 확인해서 다시 오류 페이지를 요청하게 된다.

에외 발생과 오류 페이지 요청 흐름

1.WAS <-- 필터 <-- 서블릿 <-- 인터셉터 <-- 컨트롤러(예외발생)
2.WAS /error-page/400 다시 요청 --> 필터 --> 서블릿 --> 인터셉터 --> 컨트롤러


서블릿 예외 차리 : 필터

앞서 오류가 발생하면 오류 페이지를 출력하기 위해선 WAS 내부에서 다시 한번 오류 페이지로의 호출이 발생한다. 문제는 이 과정에서 필터, 서블릿, 인터셉터가 다시 호출된다는 점이다.
따라서, 필터와 인터셉터가 중복 호출되는것을 막기 위해서는 클라이언트로부터 발생한 정상 요청이닞, 오류 페이지를 출력하기 위한 내부 요청인지 구분할 수 있어야한다.
서블릿은 이 구분을 위해서 DispatcherType이라는 추가 정보를 제공한다.

DispatcherType

package javax.servlet;

/**
 * @since Servlet 3.0
 */
public enum DispatcherType {
    FORWARD,
    INCLUDE,
    REQUEST,
    ASYNC,
    ERROR
}

실제 코드에서 DispatcherType을 보면 위 코드처럼 몇가지가 존재한다.

  • REQUEST : 클라이언트 요청
  • ERROR : 오류 요청
  • FORWARD : 서블릿에서 다른 서블릿이나 JSP 호출시
  • INCLUDE : 서블릿에서 다른 서블릿이나 JSP 결과를 포함할때
  • ASYNC : 서블릿 비동기 호출
    @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를 통해서 어떤 요청을 처리할것인지 지정할 수 있다.
위 코드는 클라이언트의 요청과 오류 페이지의 요청에서도 필터가 호출되도록 지정되었다.
setDispatcherTypes를 지정하지 않으면 기본적으로 REQEUST만 지정된다.

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

인터셉터는 서블릿이 아닌 스프링의 기술이기 떄문에 DispatcherTypes는 존재하지 않는다.
다만, 강력한 exludePathPatterns에서 지정이 가능하다.

@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/**");//오류 페이지 경로
    }
  }

스프링 부트 오류 페이지

앞선 코드들에서는 예외 처리 페이지를 만들기 위해서 여러 가지 코드를 추가해야했다.

  • WebServerCustomizer 코드 생성
  • ErrorPage 추가
  • 예외 처리용 컨트롤러 추가

스프링 부트는 위 과정을 기본적으로 제공한다.

  • /error 라는 경로로 기본 오류 페이지를 설정
    • new ErrorPage("/error"), 상태 코드와 예외를 설정하지 않으면 기본 오류 페이지로 사용
    • 서블릿 밖으로 예외가 발생하거나 response.sendError가 호출되면 /error를 호출
  • BasicErrorController라는 스프링 컨트롤러를 자동으로 등록
    • ErrorPage에서 등록한 /error를 매핑해서 처리하는 컨트롤러

따라서 개발자는 오류 페이지만 등록해주면 된다.

BasicErrorController의 처리 순서는 아래와 같다.

  1. 뷰 템플릿
  • resouces/template/XXX
  1. 정적 리소스(static, public)
  • resources/static/XXXX
  1. 적용대상이 없을댸 뷰이름

참고

해당 포스팅은 아래의 강의를 공부하여 정리한 내용입니다.
김영한님의 SpringMVC2-예외처리

0개의 댓글