Bean Scope ‐ rquest scope - Hot-stock/backend GitHub Wiki

Request Scope와 Spring에서의 동작 원리

Spring MVC에서 Request Scope는 **HTTP 요청(Request)**이 들어오는 동안에만 살아있는 객체를 의미합니다. 이를 이해하기 위해 먼저 서블릿(Servlet)과 Spring MVC의 내부 동작 방식을 살펴보겠습니다.

Request Scope란 Spring MVC에서 유지되는 방법

Request Scope는 ThreadLocal을 통해서 값이 유지됩니다. 이 ThreadLocal 객체를 조회하는 것이 RequestContextHolder입니다. RequestContextHolder가 ThreadLocal을 관리하는 방법을 보면 서블릿의 service()를 호출하게 되면 다음과 같은 흐름으로 진행됩니다.

  • DispatcherServlet -> FrameworkServlet -> process() -> ThreadLocal 관리

RequestContextHolder를 보면 ThreadLocalstatic으로 선언되어 있습니다. 그 후 processRequest()를 통해 요청을 처리하면서 ThreadLocal에 값을 넣어줍니다.

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (HTTP_SERVLET_METHODS.contains(request.getMethod())) {
            super.service(request, response);
        } else {
            processRequest(request, response);
        }
    }
}

초기화 부분: processRequest() 중 일부

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

initContextHolders(request, localeContext, requestAttributes);

Request Scope와 Proxy 객체 사용

우리는 생성자 주입 시점을 사용하기 때문에 Request Scope의 주입 시점이 굉장히 애매해집니다. 이를 해결하기 위해 스프링에서는 proxy 객체를 사용하는 방법을 사용합니다. 이 때문에 호출 시점에서 proxy 객체를 호출하면, proxy 객체는 RequestContextHolder를 사용해 값이 없으면 새로운 요청을 생성하여 ThreadLocal에 값을 삽입합니다. 이후, 우리에게는 진짜 객체처럼 동작합니다.

Proxy 객체 예제 코드

@Override
public void setUserId(String userId) {
    // 1. RequestContextHolder에서 실제 객체 조회
    UserContext actualContext = (UserContext) RequestContextHolder
        .getRequestAttributes()
        .getAttribute("scopedTarget.userContext", RequestAttributes.SCOPE_REQUEST);

    // 2. 객체가 없으면 생성
    if (actualContext == null) {
        actualContext = new UserContext();
        RequestContextHolder.getRequestAttributes()
            .setAttribute("scopedTarget.userContext", actualContext, RequestAttributes.SCOPE_REQUEST);
    }

    // 3. 실제 객체에 메서드 호출 위임
    actualContext.setUserId(userId);
}