Server/🌱 Spring Boot (java)

[Spring] 스프링 MVC 예외 처리 시 인터셉터 재호출 해결법

코딩하는 동현 2024. 8. 9. 17:28

이전 포스트에서 서블릿 예외 처리를 구현했습니다. (저번 포스트에서 코드 그대로 활용합니다.)

 

[Spring] 서블릿 예외 처리와 오류 페이지

오류 페이지의 view는 Thymeleaf 템플릿 엔진으로 작성했습니다!Dependencies: Spring Web, Lombok , Thymeleaf, Validation build.gradleplugins { id 'org.springframework.boot' version '2.5.1' id 'io.spring.dependency-management' version

konkukcodekat.tistory.com

 

 

저번 포스트에서 마지막에 동작 과정을 보면 아래와 같습니다.

1. WebServerCustomizer <- WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- ServletExController(sendError 호출)

2. WAS (/error-page/500 다시 요청) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500) -> 타임리프 View(클라이언트에게 보여주는 페이지)

 

오류가 발생하면 오류 페이지를 출력하기 위해서 2번과 같이 WAS내부에서 호출이 다시 한번 발생하고, 그 과정에서 필터, 인터셉터등이 모두 호출됩니다.

로그인 인증 체크 같은 경우, 이미 한번 필터나 인터셉터에서 로그인 체크를 완료한 상태이기 때문에, 서버 내부에서 오류 페이지를 호출하면서 똑같은 필터나 인터셉터가 한번 더 호출되는 것은 비효율적입니다.

결국 클라이언트로 부터 발생한 정상 요청인지, 아니면 오류 페이지를 출력하기 위해서 내부 요청인지 구분해야 다시 호출되는 것을 막을 수 있습니다.

스프링 MVC 인터셉터는 excludePathPatterns를 이용해서 호출을 막을 수 가 있습니다.


WebConfig에 DispatcherType 추가

코드는 이전 서플릿 인터셉터 포스트에 코드를 가져와서 수정하겠습니다. (logInterceptor, WebConfig)

 

[Spring] 서블릿 인터셉터 개념과 로그인(인증) 필터 구현

로그인 기능을 구현해도 로그인된 사용자에 맞춰서 기능을 구현하는 것 까진 잘되는데, 특정 페이지에 접근하는 것을 막는것은 불가능합니다. 예를 들면 로그인하지 않은 사람이 '회원정보 수

konkukcodekat.tistory.com

 

WebConfig.java

package hello.exception;

import hello.exception.filter.LogFilter;
import hello.exception.interceptor.LogInterceptor;
import hello.exception.resolver.MyHandlerExceptionResolver;
import hello.exception.resolver.UserHandlerExceptionResolver;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    // 인터셉터는 dispatcherType를 못쓰므로 /error-page exclude 하는 방식
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "*.ico", "/error", "/error-page/**");//오류 페이지 경로
    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new MyHandlerExceptionResolver());
        resolvers.add(new UserHandlerExceptionResolver());
    }
}

필터의 경우에는 필터를 등록할 때 어떤 DispatcherType인 경웅에 필터를 적용할 지 선택할 수 있었습니다.

그런데 인터셉터는 서블릿이 제공하는 기능이 아니라 스프링이 제공하는 기능이므로 DispatcherType과 무관하게 항상 호출 됩니다.

 

대신 인터셉터는 위와 같이 요청경로에 따라서 추가하거나 제외하기 쉽게 돼있기 때문에, excludePathPatterns를 사용해서 오류페이지 요청들을 전부 제외 시켜주면 됩니다.


LogInterceptor.java

 

DispatcherType 로그를 추가했습니다.

package hello.exception.interceptor;

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;
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("REQUEST  [{}][{}][{}][{}]", uuid, request.getDispatcherType(), requestURI, handler);
        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("RESPONSE [{}][{}][{}]", logId, request.getDispatcherType(), requestURI);
        if (ex != null) {
            log.error("afterCompletion error!!", ex);
        }
    }
}

실행결과

1. WebConfig에서 excludePathPatterns("/css/**", "*.ico", "/error");를 그대로 나뒀을때

/error-404 에 접속했을때 똑같이 두번 재호출 되는 문제를 확인할 수 있습니다.

 

2. excludePathPatterns("/css/**", "*.ico", "/error", "/error-page/**"); 를 이용해서 /error-page/** 를 제외했을때

/error-404 에 접속했을때 1번과 다르게 재호출이 되지 않는것을 확인 할 수 있습니다.


정리

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

excludePathPatterns를 통해 4번에서 인터셉터가 재호출되는 것을 막을 수 있습니다.

반응형