프레임워크/Spring

[Spring Framework] 오류 페이지 처리 1탄

코드몬스터 2023. 10. 18. 14:05
728x90
해당 글은 인프런 김영한 강사님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 을 보고 정리한 내용입니다.

 

 

자바 실행

자바의 main 메서드를 실행하는 경우 main 이라는 이름의 쓰레드가 실행된다.

에러가 발생하고 잡지 못하면 main 쓰레드가 예외 정보를 남기고 종료된다.

 

웹 애플리케이션

사용자 요청 별로 별도의 쓰레드가 할당 되고, 서블릿 컨테이너 안에서 실행된다.

try ~ catch로 예외를 잡으면 아무런 문제가 없지만 잡지 못하고 서블릿 밖으로 전달되면 


서블릿의 오류 페이지 요청 흐름

  1. WAS → 필터 → 서블릿 → 인터셉터 → 컨트롤러                            ⇒ 정상요청
  2. WAS ← 필터 ← 서블릿 ← 인터셉터 ← 컨트롤러(예외발생)           ⇒ 예외발생
  3. WAS “/error-page/500" → 필터 → 서블릿 → 인터셉터 → 컨트롤러(/error-page/500) → View  ⇒ 에러페이지 호출

 

위와 같이 클라이언트로 부터 정상요청을 받았는데 내부에서 어떠한 400 또는 500 등의 에러가 발생하고 예외에 잡히게 된다. 그러면 정상 요청에 대한 오류 응답이 WAS까지 전달 되고 WAS 는 다시 에러 페이지 호출을 위한 컨트롤러에 요청을 보내게 된다.

 

즉, 예외에서 컨트롤로 한 번(예외처리) 그리고 WAS에서 컨트롤(에러 페이지 호출)로 두 번의 호출이 일어나게 된다.

 

오류 페이지를 보여주기 과정에서 클라이언트의 요청인지 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분이 필요한데 이를 위해 서블릿은 DispatcherType 이라는 추가 정보를 제공한다.

 

DispatcherType 종류

REQUEST: 클라이언트 요청

ERROR: 오류 요청

FORWARD: 서블릿에서 다른 서블릿이나 JSP를 호출할 때

INCLUDE: 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때

ASYNC: 서블릿 비동기 호출

 

정상 및 오류 요청 흐름 구별

※ 해당 방법은 서블릿이 제공하는 필터에서만 적용이 가능합니다.

 


오류 페이지 처리 방법

  • 필터는 DispatcherType  으로 중복호출 제거
  • 인터셉터는 경로 정보로 중복 호출 제거(excludePathPatterns("/error/**"))

소제목과 요청 흐름에서 보면 서블릿을 기준으로 왼쪽의 필터와 오른쪽의 인터셉터에서 처리 할 수 있다.

(필터는 서블릿이 제공하고 인터셉터는 스프링이 제공하는 기능이다.)

 

 

필터(Filter)

https://docs.oracle.com/cd/A97329_03/web.902/a95878/filters.htm

 

Filter 인터페이스

  • package 의 경로를 보면 javax.servlet 으로 서블릿(servlet)이 제공하는 것을 알 수 있다.
  • 해당 인터페이스를 구현하려면 init, doFilter 그리고 destory 메서드를 구현해야한다.
package javax.servlet;

import java.io.IOException;

public interface Filter {
    void init(FilterConfig var1) throws ServletException;

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    void destroy();
}

 

Filter 를 구현한 LogFilter 클래스

  • 해당 클래스는 개발자가 직접 구현을 하면 된다.
    즉, 클래스 이름은 알아서 정하면 된다.
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import java.io.IOException;

@Slf4j
public class LogFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
		
    }

    @Override
    public void doFilter(ServletRequest requset, ServletResponse response, 
    	FilterChain filterChain) throws IOException, ServletException 
    {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        try {
            log.info("Request [{}][{}]", request.getDispatcherType(), requestURI);
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        }
    }

    @Override
    public void destroy() {

    }
}

 

webConfig 클래스

  • setDispatcherTypes의 default 는 REQUEST 이다.
    • 따라서, 에러에 대해서 해당 필터는 두 번 호출 되지 않는다.
    • 만약, 호출을 원한다면 DispatcherType.ERROR를 추가하면 된다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;

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

}

 

인터셉터(Intercepter)

인터셉터 클래스

  • preHandle
  • postHandle
    • 에러가 발생하면 호출이 안된다.
  • afterCompletion
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();

        log.info("Request [{}][{}][{}]", request.getDispatcherType(), requestURI, handler);

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

 

webConfig 클래스

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "*.icon", "/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;
    }
}

'프레임워크 > Spring' 카테고리의 다른 글

API(Application Programming Interface)  (0) 2023.04.22
어노테이션 for 스프링  (0) 2023.04.15
controller, service, repository  (0) 2023.04.12
DDD 설계 vs SQL 설계  (0) 2023.04.10
VO / DTO / Entity  (0) 2023.04.09