zenn.skin 무료버전 배포중!
자세히보기

Web/Spring

[Spring Web 6.1] DispatcherServlet 뜯어보기 - Deprecated & Static

koosco! 2024. 10. 4. 12:10

Spring을 공부하면서 라이브러리를 직접 뜯어봐도 재미있을 것 같아 시간이 날 때마다 하나씩 뜯어보려고 한다.

워낙 코드가 많기 때문에 web 서버에서 빠질 수 없는 Spring web의 dispatcherServlet의 코드를 보려고 한다.

DispatcherServlet 코드만 900줄 가까이 되기 때문에 이론상으로만 알고 있던 내부 로직을 코드로 직접 뜯어보고 느껴보는 것도 많은 도움이 될 것 같아.

Spring Web 6.1을 기준으로 코드를 분석해보려 하고, deprecated된 코드부터 살펴보려 한다.

변수에 대한 내용은 그 때, 그 때 메서드를 분석할 때마다 같이 공부해보려 한다.

 

Deprecated

1) setThrowExceptionIfNoHandlerFound

/** @deprecated */
@Deprecated(
    since = "6.1",
    forRemoval = true
)
public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) {
    this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
}

이 함수는 throwExceptionIfNoHandlerFound를 설정하는 setter 메서드이다. noHandlerFound 메서드에서 throwExceptionIfNoHandlerFound를 호출하게 되는데, DispatcherServlet에서 기본값으로 설정한 값은 true이다.

noHandlerFound 메서드에서 값이 설정되었을 때 NoHandlerFoundException을 던질지 여부를 결정하는 값인데 6.1버전부터 setter를 deprecated시켰다. 아마도 NoHandlerFoundException을 던지도록 강요하려고 setter를 deprecated시킨 것 같다.

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        Log var10000 = pageNotFoundLogger;
        String var10001 = request.getMethod();
        var10000.warn("No mapping for " + var10001 + " " + getRequestUri(request));
    }

    if (this.throwExceptionIfNoHandlerFound) {
        throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), (new ServletServerHttpRequest(request)).getHeaders());
    } else {
        response.sendError(404);
    }
}
public class NoHandlerFoundException extends ServletException implements ErrorResponse {
    private final String httpMethod;
    private final String requestURL;
    private final HttpHeaders requestHeaders;
    private final ProblemDetail body;

    public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders headers) {
        super("No endpoint " + httpMethod + " " + requestURL + ".");
        this.httpMethod = httpMethod;
        this.requestURL = requestURL;
        this.requestHeaders = headers;
        this.body = ProblemDetail.forStatusAndDetail(this.getStatusCode(), this.getMessage());
    }

    public HttpStatusCode getStatusCode() {
        return HttpStatus.NOT_FOUND;
    }
}

NoHandlerFoundException을 보면 ServletException으로, http method와 그에 해당하는 handler를 찾지 못했을 때 던지는 예외로 404 상태 코드를 던지는 것을 알 수 있다.

 

2) Theme 관련 메서드

/** @deprecated */
@Deprecated
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";

/** @deprecated */
@Deprecated
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";

/** @deprecated */
@Deprecated
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";

/** @deprecated */
@Deprecated
@Nullable
private ThemeResolver themeResolver;

/** @deprecated */
@Deprecated
private void initThemeResolver(ApplicationContext context) {
    try {
        this.themeResolver = (ThemeResolver)context.getBean("themeResolver", ThemeResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Detected " + this.themeResolver);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
        }
    } catch (NoSuchBeanDefinitionException var3) {
        this.themeResolver = (ThemeResolver)this.getDefaultStrategy(context, ThemeResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No ThemeResolver 'themeResolver': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
        }
    }

}

/** @deprecated */
@Deprecated
@Nullable
public final ThemeSource getThemeSource() {
    WebApplicationContext var2 = this.getWebApplicationContext();
    ThemeSource var10000;
    if (var2 instanceof ThemeSource themeSource) {
        var10000 = themeSource;
    } else {
        var10000 = null;
    }

    return var10000;
}

 

 

ThemeResolver (Spring Framework 6.1.13 API)

Interface for web-based theme resolution strategies that allows for both theme resolution via the request and theme modification via request and response. This interface allows for implementations based on session, cookies, etc. The default implementation

docs.spring.io

Spring docs를 보면 6.0부터 ThemeResolver를 지원하지 않는다고 한다. 찾아보니 Spring WebThemeResolver는 쿠키에 설정되어 있는 Theme값을 이용하여 브라우저의 테마를 설정하기 위한 설정이라는데 docs를 보면 ThemeResolver를 사용하지 말고 css를 사용하라고 나와있다.

 

 

static method

hasMultipartException

private static boolean hasMultipartException(HttpServletRequest request) {
    for(Throwable error = (Throwable)request.getAttribute("jakarta.servlet.error.exception"); error != null; error = error.getCause()) {
        if (error instanceof MultipartException) {
            return true;
        }
    }

    return false;
}

request에 있는 error가 MultipartException인지를 판단하는 메서드이다. Multipart를 이용한 파일 요청에 대해서는 별도의 포스팅에서 다룰 예정이다.

 

triggerAfterCompletion

private static void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }

    throw ex;
}

mapperHandler는 HandlerExecutionChain 클래스이다.

HandlerExecutionChain은 Spring MVC에서 요청을 처리하기 위한 핸들러와 해당 핸들러에 연결된 인터셉터 체인(Interceptor Chain)을 관리하는 역할을 한다.

  • HandlerInterceptor 인터페이스를 구현한 인터셉터들을 관리, 인터셉터들은 요청 전/후, 완료 시점에 특정 로직을 수행
  • triggerAfterCompletion 메서드를 호출하면 인터셉터의 afterCompletion 메서드가 실행되어 리소스를 정리하거나, 로깅 등의 작업을 수행

 

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for(int i = this.interceptorIndex; i >= 0; --i) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable var7) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
        }
    }

}

HandlerExecutionChain의 triggerAfterCompletion 메서드를 살펴보면 자신이 관리하고 있는 interceptor 리스트들에게 요청, 응답, handler, 예외를 넘겨주게 된다. handler는 현재 요청을 처리하고 있는 Controller의 메서드를 나타낸다.

HandlerExecutionChain의 handler 등록

DispatcherServlet은 클라이언트로부터 요청을 받으면 HandlerMapping을 이용하여 해당 요청을 처리하는 handler(Controller의 메서드)를 찾은 후에 HandlerExecutionChain의 handler 필드로 설정하고 이와 관련한 인터셉터를 설정하는 식으로 동작합니다.

 

HandlerInterceptor 인터페이스 코드에 대해 좀 더 살펴보면, interceptor는 controller 실행 이전에 실행되는 preHandle 메서드와 controller 실행 이후에 실행되는 postHandle 메서드를 가진다. default로 선언되어있기 때문에 요청 전/후 처리하는 interceptor를 하나의 인터페이스로 관리할 수 있도록 코드가 작성되어 있다.

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

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

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

HandlerExecutionChain에서 나타내는 interceptor는 controller에서 요청 처리를 한 후에 동작하는 interceptor로 postHandle과 afterCompletion을 구현하게 된다.

인터페이스의 구현체로는 위 세 개가 있다. MappedInterceptor는 특정 경로로 매핑된 인터셉터의 후처리, WebContentInterceptor는 응답 헤더와 관련된 후처리, WebRequestHandlerInterceptorAdapter는 비동기 요청에 대한 후처리를 하게 된다.

직접적으로 구현되어 있는 코드는 없고, 해당 인터페이스들을 상속받아서 에러 처리를 하고 싶을 때 이용할 수 있는 것 같다.

 

getRequestUri

private static String getRequestUri(HttpServletRequest request) {
    String uri = (String)request.getAttribute("jakarta.servlet.include.request_uri");
    if (uri == null) {
        uri = request.getRequestURI();
    }

    return uri;
}

클라이언트 요청에서 uri를 추출하기 위한 메서드이다.

'Web/Spring'의 다른글

  • 현재글 [Spring Web 6.1] DispatcherServlet 뜯어보기 - Deprecated & Static

관련글