SpringBoot - Dispatcher Servlet을 활용해 매핑되는 핸들러 구하기

Dispatcher Servlet을 활용해 매핑되는 핸들러 구하기

개요

  • 최근에 스프링부트를 활용한 서버 Application 프로젝트를 진행하던 도중, 아래와 같은 요구사항이 추가되었다.
    • 인터셉터를 통해 404 에러를 처리하라.
    • 그러기 위해선, 서버로 전달된 HTTP 요청 메시지의 URI(Endpoint)에 따라, 어떤 컨트롤러가 매핑되는지 분석한다.
    • 그리고 그 결과를 기반으로 응답 메시지를 다르게 전송한다.
  • 단순히 해당 요구사항만 본다면, “컨트롤러 계층이나 서비스 계층에서 처리를 하면 되는 간단한 문제아닌가?” 라고 생각할 수 있다.
  • 하지만 이 로직을 인터셉터 단에서 처리하도록 해야하는 상황이었다.

프로젝트에서 해당 요구사항이 추가된 이유는 “로그인 인터셉터보다 먼저 해당 로직이 동작”해야하기 때문이었다.
따라서 컨트롤러 단에서 처리할 수 없었다.

보다 자세한 내용은 관련 이슈를 참고하자.

  • 따라서 관련 사항을 프로젝트에서 구현하기 위해, 공부한 내용을 정리하고자 한다.

  • 자세한 설명을 위해, 실제 소스코드를 먼저 보자.

    • 실제로 프로젝트에서 사용된 코드 일부를 그대로 가져왔다.
    • 해당 소스코드에선 인터셉터가 활용되었지만, 핵심 로직을 위주로 참고하자.


소스코드

NoHandlerFoundInterceptor 인터셉터

참고로 인터셉터 관련 내용은 해당 포스팅 을 참고하자.

@RequiredArgsConstructor
@Component
public class NoHandlerFoundInterceptor implements HandlerInterceptor {

  private final DispatcherServlet dispatcherServlet;

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String uri = request.getRequestURI();
    if (null == getHandler(request)) {
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
      return false;
    }
    return true;
  }

  protected HandlerExecutionChain getHandler(HttpServletRequest request) {
    if (dispatcherServlet.getHandlerMappings() != null) {
      for (HandlerMapping mapping : dispatcherServlet.getHandlerMappings()) {
        try {
          HandlerExecutionChain handlerExecutionChain = mapping.getHandler(request);
          if (!handlerExecutionChain.getHandler().toString().contains("ResourceHttpRequestHandler")) {
            return handlerExecutionChain;
          }
        } catch (Exception e) {
          //ignore
        }
      }
    }

    return null;
  }

}


상세 설명

implements HandlerInterceptor

  • HandlerInterceptor 는 인터셉터를 구현하기 위한 인터페이스이다.
  • 인터셉터 관련 사항은 본 글의 범위를 넘어가니 생략하겠다.

private final DispatcherServlet dispatcherServlet

  • 생성자 주입을 통해, DispatcherServlet Bean 객체를 주입받았다.
  • 해당 Bean 객체가 핵심 역할을 하게 된다. 해당 객체가 제공하는 메서드를 통해 매핑되는 핸들러(컨트롤러)를 찾을 수 있다.
  • 여기서 주입받은 DispatcherServlet Bean 객체는 실제로 스프링 구조에서 사용되는 객체이다.
  • DispatcherServlet(디스패처 서블릿)은 HTTP 요청 메시지를 적절한 핸들러(컨트롤러)에게 전달하는 역할을 수행한다.
  • 즉 수문장 역할을 수행한다고 생각하면 된다.
  • 이것을 그림으로 표현하면 다음과 같다.

디스패처 서블릿을 포함한 스프링 MVC 구조에 대한 내용은 해당 포스팅을 참고하자.

HandlerExecutionChain 메서드

  • 해당 메서드에 핵심 로직이 들어있다. 본격적으로 알아보자.
  • dispatcherServlet.getHandlerMappings()
    • 해당 메서드를 통해, 핸들러 매핑 정보가 담긴 Collection 객체를 반환받을 수 있다.
  • mapping.getHandler(request)
    • 매개변수로 HttpServletRequest 객체를 전달하므로써, 해당 요청이 어떤 핸들러(컨트롤러)와 매핑되는지 알 수 있다.
    • 만약 사용자(개발자)가 정의(구현)한 핸들러 중, 매핑되는 핸들러가 없다면 ResourceHttpRequestHandler 관련 정보를 반환한다.
      • ResourceHttpRequestHandler 은 정적 리소스를 응답하는 기본 핸들러이다. 스프링 자체에서 제공하는 핸들러이다.
      • 만약 서버에 들어온 요청이 아무런 핸들러와도 매핑되지 않는다면, 해당 핸들러와 매핑되도록 기본적으로 설정되어 있다.


정리

  • 스프링 구조에서 수문장 역할을 하는 DispatcherServlet Bean 객체를 주입받아, 관련 메서드를 사용할 수 있다.
  • DispatcherServlet가 요청 메시지를 어떤 핸들러에게 보낼지(매핑할지) 결정한다.
  • 따라서 해당 객체를 활용하여, 들어온 요청이 어떤 핸들러(컨트롤러)와 매핑되는지 알 수 있다.
  • 핵심 메서드
    • getHandlerMappings() : DispatcherServlet 의 인스턴스 메서드
    • getHandler(HttpServletRequest) : HandlerMapping 의 인스턴스 메서드
  • 필자는 “인터셉터를 통해 404 에러를 처리”하기 위해, 본 로직을 사용했다.
    본인과 같은 경우가 아니더라도, DispatcherServlet에 접근하여 다양한 문제를 해결할 수 있을 것으로 보인다.



Reference