예외 응답 처리 과정 Servlet VS Spring [예외 페이지 응답 편]

김용현·2024년 1월 30일
0

Spring

목록 보기
11/13

본 포스트는 김영한 님의 스프링 MVC 2편-백엔드 웹 개발 활용 기술 강의를 토대로 작성하였습니다.

서블릿 예외 처리

서블릿은 다음 2가지 방식으로 예외 처리를 지원한다.

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

하나씩 알아보자

Excetpion

중간에 try-catch문이나 별 다른 예외 처리를 하지 않는다면 발생한 예외는 그대로 WAS까지 올라간다. 즉 서블릿 컨테이너(Tomcat)까지 올라간다. 이후 처리 과정은 뒤에서 다시 알아보자.

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

오류가 발생했을 때 HttpServletResponse가 제공하는 sendError 메소드를 사용해도 된다. 이 메소드는 함수명 그대로 예외를 전달하는 함수로 서블릿 컨테이너에 예외가 발생했다는 사실을 알리는 기능을 한다.

오류 페이지 작동 원리

위의 예외 전달 흐름을 보면 최종적으로 WAS에서 에러를 확인한다. 이 때 예외에 오류 페이지를 확인하고 이 페이지를 다시 서버에 요청한다. 이후 요청된 예외 페이지를 클라이언트로 내보낸다.

그런데 여기서 WAS에서 오류 페이지를 요청하는 과정을 보면 중간에 필터와 인터셉터가 똑같이 호출되는 것을 볼 수 있다. 그러나 필터와 인터셉터는 굳이 한 번 더 호출될 필요가 없다. 따라서 서블릿은 DispatcherType 이라는 정보를 제공한다.

DispatcherType은 처음 클라이언트 요청일 경우 안에 REQUEST 값이 들어간다. 그러나 오류 페이지를 요청하는 경우 ERROR이 들어간다. 이 외에도 enum으로 정의된 값들을 보면

//jakarta.servlet.DispatcherType
public enum DispatcherType {
     FORWARD,
     INCLUDE,
     REQUEST,
     ASYNC,
     ERROR
}

REQUEST : 클라이언트 요청
ERROR : 오류 요청
FORWARD : MVC에서 배웠던 서블릿에서 다른 서블릿이나 JSP를 호출할 때 RequestDispatcher.forward(request, response);
INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때 RequestDispatcher.include(request, response);
ASYNC : 서블릿 비동기 호출

다음과 같다.

필터 호출을 제외하는 방법

package hello.exception;
 import hello.exception.filter.LogFilter;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 
 @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);
         
         return filterRegistrationBean;
	} 
}

다음 예시 코드를 보면 setDispatcherTypesREQUESTERROR 를 넘기고 있다. 이는 REQUEST, ERROR일 때 해당 필터를 호출하겠다는 의미이다.
디폴트 값은 REQUEST만 들어가며 필요한 경우 위 코드처럼 조건을 추가하면 된다.

인터셉터 호출을 제외하는 방법

필터는 위처럼 DispatcherType에 따라 호출 여부를 결정할 수 있었다. 그러나 인터셉터는 서블릿이 아닌 스프링이 제공하는 기능이다. 따라서 DispatcherType을 이용할 수 없다.

package hello.exception;
 import hello.exception.filter.LogFilter;
 import hello.exception.interceptor.LogInterceptor;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 
 @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/**" //오류 페이지 경로
	); 
}
	//@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;	
	} 
}

그래서 스프링에서는 위 예시 코드와 같이 인터셉터를 원하는 경로에만 적용할 수 있도록 지원한다. excludePathPatterns의 경우 이름에서도 알 수 있듯이 해당 경로로 오는 요청은 인터셉터 호출을 제외하겠다는 의미이다.

따라서 흐름을 정리하면 다음과 같다.

스프링 부트에서 제공하는 오류 페이지

스프링 부트에서는 이러한 오류 처리 과정을 기본으로 제공한다.
BasicErrorController 라는 스프링 컨트롤러를 자동으로 등록하며
ErrorPage에서 등록한 /error 를 매핑해서 처리하는 컨트롤러이다.

참고❗️
ErrorMvcAutoConfiguration 이라는 클래스가 오류 페이지를 자동으로 등록하는 역할을 한다.

따라서 개발자는 오류 페이지 화면만 만들면 된다. BasicErrorController에서 오류 페이지를 찾는 우선순위는 다음과 같다.

  1. 뷰 템플릿 (templates 하위 디렉토리)
  2. 정적 리소스 (static, public)
  3. 적용 대상이 없을 때 뷰 이름 (error)

따라서 위 적용 순서에 따라 적절하게 오류 페이지를 만들어 넣어놓으면 된다.

참고❗️

  • timestamp: Fri Feb 05 00:00:00 KST 2021
  • status: 400
  • error: Bad Request
  • exception: org.springframework.validation.BindException
  • trace: 예외 trace
  • message: Validation failed for object='data'. Error count: 1
  • errors: Errors(BindingResult)
  • path: 클라이언트 요청 경로 (/hello)

BasicErrorController 에서는 다음과 같은 정보들을 model에 담아서 뷰에 전달한다.
참고하자

profile
평생 여행 다니는게 꿈 💭 👊 😁 🏋️‍♀️ 🦦 🔥

0개의 댓글

관련 채용 정보