[Spring MVC] 스프링 인터셉터의 개념과 호출 흐름

clean·2024년 4월 11일
0

Spring MVC

목록 보기
2/2
post-thumbnail

스프링 인터셉터란

"인터셉트"는 낚아챈다는 의미입니다. 스프링 인터셉터는 컨트롤러의 핸들러가 호출되기 전에 요청을 낚아채서 요청에 대한 내용을 가공, 변경할 수 있게 하는 기술입니다.

예를 들어서 어떤 유저가 쇼핑몰에서 주문하기 요청을 보냈는데, 그 유저가 로그아웃 상태라고 해봅시다.
이 쇼핑몰은 로그인을 한 회원만이 상품을 주문할 수 있습니다. 그러면 이 회원에게 주문 페이지를 보여주는 대신 로그인 페이지를 띄워주거나 하는 조치를 해야겠죠.
그럴 때 인터셉터를 활용할 수 있습니다. 사용자의 요청을 가로채서 로그인이 되어있는지 확인하고 안되어 있다면 요청을 변경해서 로그인 페이지로 대신 보내주는 기능을 구현할 수 있는 것입니다.

스프링 인터셉터의 흐름

스프링 인터셉터는 디스패처 서블릿 호출 직후, 컨트롤러 호출 직전에 호출됩니다.

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러

스프링 인터셉터는 스프링 MVC가 제공하는 기능이기 때문에 결국 디스패처 서블릿 이후에 호출되게 됩니다. 스프링 MVC의 시작이 디스패처 서블릿이라고 생각한다면 이해가 쉽습니다.

스프링 인터셉터는 마치 서블릿 필터처럼 여러 인터셉터의 체인으로 구성됩니다.
예를 들면 로그를 남기는 인터셉터 후에 로그인 체크 인터셉터 이런 식으로 자유롭게 인터셉터들을 추가할 수 있고 실행 순서를 지정할 수 있습니다.

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

더 자세한 흐름 - 정상 상태일때

스프링 인터셉터가 호출되는 더 자세한 흐름에 대해 알아보겠습니다.
스프링 인터셉터 인터페이스에는 prehandle, posthandle, afterCompletion이라는 세가지 메소드가 정의되어 있습니다.
각각의 메소드가 호출되는 시점이 다른데요. 그 시점은 아래와 같습니다.

  1. prehandle(): 핸들러 어댑터 호출 직전. 즉 핸들러(컨트롤러) 호출 직전이다.
  2. posthandle(): 컨트롤러 호출 직후에 호출됩니다. 정확히는 핸들러(컨트롤러)가 호출된 후에 핸들러 어댑터 호출 후입니다.
  3. afterCompletion(): 뷰가 렌더링 된 이후에 호출됩니다.

더 자세한 흐름 - 에러가 발생했을 때

prehandle, posthandle, afterCompletion 이 세 메소드의 호출 흐름이 더 중요해지는 때는 바로 에러가 발생하는 상황입니다.

요청에 문제가 있다거나, 컨트롤러나 서비스 부분을 잘못 구현했다거나 하면 핸들러(컨트롤러)를 호출한 후에 에러가 발생할 것입니다.

prehandle은 컨트롤러 호출 전에 호출되므로 정상적으로 호출되지만, 컨트롤러에서 에러가 발생하면 posthandle은 호출되지 않습니다.

하지만 에러가 발생하더라도 afterCompletion은 호출이 됩니다.

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

afterCompletion 메소드의 헤더 부분을 보면, Exception ex가 매개변수로 있는 것을 확인할 수 있습니다.
이 매개변수를 이용해서 에러 상황에서 어떤 익셉션이 발생한 건지도 로그로 찍어서 확인이 가능합니다.
정상 상황일 때 이 매개변수는 null이 넘어오게 됩니다.

스프링 인터셉터의 구현

스프링 인터셉터를 만들려면 HandlerInterceptor 인터페이스를 구현하고 preHandle, postHandle, afterCompletion 이 세가지 메소드를 구현해주면 됩니다.

김영한님의 강의에서는 전체 프로젝트에 대해서 요청에 대한 로그를 찍어보는 간단한 인터셉터를 구현해보았습니다.

LogIntegerceptor.java

@Slf4j
public class LogInterceptor implements HandlerInterceptor {
  private 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); // afterCompletion으로 uuid를 넘기기 위함
    // request는 하나의 사용자에 대해서 같음이 보장이 되기 때문이다.

    if(handler instanceof HandlerMethod) {
      HandlerMethod hm = (HandlerMethod) handler; // 호출할 컨트롤러 메소드의 모든 정보가 포함되어 있다.
    }

    log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
    return true;

  }

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

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

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

  }
}

이렇게 만든 인터셉터는 설정 파일에 반드시 등록을 해주어야 동작합니다.
@Configuration 어노테이션이 붙어 있고, WebConfigurer 인터페이스를 구현한 Config class에 addInterceptors() 라는 메소드를 구현하여 등록해줍니다.

addInterceptors()WebConfiguer에 정의되어 있는 디폴트 메소드입니다. (디폴트 메소드여서 필수로 구현하지 않아도 에러가 발생하지 않는 것입니다.)

@Configuration
public class WebConfig implements WebMvcConfigurer {
  // 인터셉터 추가
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor())
        .order(1)
        .addPathPatterns("/**") // 하위 디렉토리에 모두 적용
        .excludePathPatterns("/css/**", "/*.ico", "/error");
  }
}

간단한 요청을 보내면 로그가 잘 찍히는 것을 확인할 수 있었습니다.

Reference

profile
블로그 이전하려고 합니다! 👉 https://onfonf.tistory.com 🍀

0개의 댓글