🍿 강의수강/스프링MVC2편 🔒

[스프링MVC2편] 섹션 8. 예외 처리와 오류 페이지 - DispatcherType과 인터셉터

케로⸝⸝◜࿀◝ ⸝⸝ 2024. 6. 12. 12:22

1. DispatcherType과 인터셉터

실습

LogInterceptor

package me.progfrog.exception.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.util.UUID;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    public static final String LOG_ID = "logId";

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

        String uuid = UUID.randomUUID().toString();
        request.setAttribute(LOG_ID, uuid);

        log.info("INTERCEPTOR REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle [{}]", modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String requestURI = request.getRequestURI();
        String logId = (String) request.getAttribute(LOG_ID);
        log.info("INTERCEPTOR RESPONSE [{}][{}][{}]", logId, request.getDispatcherType(), requestURI);

        if (ex != null) {
            log.error("afterCompletion error!!", ex);
        }
    }
}

 

WebConfig

  • 필터의 경우는 필터를 등록할 때 어떤 DispatcherType인 경우에 필터를 적용할 지 선택할 수 있었다.
  • 그런데 인터셉터는 서블릿이 제공하는 기능이 아니라, 스프링이 제공하는 기능이기 때문에 DispatcherType과 무관하게 항상 호출된다.
  • 인터셉터의 excludePathPatterns를 사용해서 오류 페이지 경로(/error-page/**)를 제외시켜주자! 
package me.progfrog.exception;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import me.progfrog.exception.filter.LogFilter;
import me.progfrog.exception.interceptor.LogInterceptor;
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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/error", "/error-page/**");
    }
}

 

확인

예외 발생 시

  • preHandle: 컨트롤러 호출 전에 호출된다.
  • postHandle: 컨트롤러에서 예외가 발생하면, postHandle은 호출되지 않는다.
  • afterCompletion: afterCompletion은 항상 호출된다. 이 경우 예외(ex)를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다.

따라서, preHandle의 로그와 afterCompletion로그가 남아있는 것을 확인할 수 있다.

 

만약, /error-page/**를 포함해서 호출하도록 한다면...

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/error");
    }

아래와 같이, 예외가 WAS까지 올라왔다가 오류 페이지를 출력하기 위해 WAS 내부에서 다시 호출이 발생하는 로그도 확인할 수 있다. 이때는 인터셉터 입장에서는 정상 호출 postHandle로그도 남아있는 것 까지 확인!

 

2. 정리

/hello 정상 요청

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

 

/error-ex 오류 요청

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

1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS
오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트롤러(/error-page/500) -> View

 

 

 

반응형