Backend Development 7 min read

Uncovering How SpringBoot Injects HttpServletRequest via ThreadLocal and Dynamic Proxies

This article explains how SpringBoot injects HttpServletRequest into controllers using a JDK dynamic proxy and ThreadLocal storage, tracing the request object from the ObjectFactoryDelegatingInvocationHandler through RequestObjectFactory, RequestContextHolder, and the FrameworkServlet's processRequest lifecycle.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Uncovering How SpringBoot Injects HttpServletRequest via ThreadLocal and Dynamic Proxies

Environment: SpringBoot 2.3.9.RELEASE

Test Controller

<code>@RestController
@RequestMapping("/message")
public class MessageController {
    @Resource
    private HttpServletRequest request;

    @PostMapping("/resolver")
    public Object resolver(@RequestBody Users user) {
        System.out.println(request);
        return user;
    }
}</code>

When debugging, the injected

request

object is actually a proxy instance of

ObjectFactoryDelegatingInvocationHandler

. The handler delegates to a

RequestObjectFactory

which creates the real

HttpServletRequest

object.

ObjectFactoryDelegatingInvocationHandler

<code>private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
    private final ObjectFactory<?> objectFactory;

    public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
        this.objectFactory = objectFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("equals")) {
            // Only consider equal when proxies are identical.
            return (proxy == args[0]);
        } else if (methodName.equals("hashCode")) {
            // Use hashCode of proxy.
            return System.identityHashCode(proxy);
        } else if (methodName.equals("toString")) {
            return this.objectFactory.toString();
        }
        try {
            return method.invoke(this.objectFactory.getObject(), args);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}</code>

RequestObjectFactory

<code>@SuppressWarnings("serial")
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
    @Override
    public ServletRequest getObject() {
        return currentRequestAttributes().getRequest();
    }

    @Override
    public String toString() {
        return "Current HttpServletRequest";
    }
}</code>

currentRequestAttributes (primary)

<code>private static ServletRequestAttributes currentRequestAttributes() {
    RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
    if (!(requestAttr instanceof ServletRequestAttributes)) {
        throw new IllegalStateException("Current request is not a servlet request");
    }
    return (ServletRequestAttributes) requestAttr;
}</code>

currentRequestAttributes (fallback)

<code>public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
    RequestAttributes attributes = getRequestAttributes();
    if (attributes == null) {
        if (jsfPresent) {
            attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
        }
        if (attributes == null) {
            throw new IllegalStateException(
                "No thread-bound request found: " +
                "Are you referring to request attributes outside of an actual web request, " +
                "or processing a request outside the originally receiving thread? " +
                "If you are actually operating within a web request and still receive this message, " +
                "your code is probably running outside of DispatcherServlet: " +
                "In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
        }
    }
    return attributes;
}</code>

getRequestAttributes

<code>public static RequestAttributes getRequestAttributes() {
    RequestAttributes attributes = requestAttributesHolder.get();
    if (attributes == null) {
        attributes = inheritableRequestAttributesHolder.get();
    }
    return attributes;
}</code>

The real

HttpServletRequest

is stored in a

ThreadLocal

variable, which is bound to the current thread. The following trace shows how the request object is placed into this

ThreadLocal

during the servlet processing lifecycle.

Servlet processing flow

Request →

service()

doXXX()

Spring MVC’s core controller

DispatcherServlet

extends

FrameworkServlet

. Each

doXXX

method invokes

processRequest

.

processRequest method

<code>protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);
    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);
    try {
        doService(request, response);
    } catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    } catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    } finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}</code>

Two lines create the

ServletRequestAttributes

object:

<code>RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);</code>

buildRequestAttributes

<code>protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request,
        @Nullable HttpServletResponse response, @Nullable RequestAttributes previousAttributes) {
    if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
        return new ServletRequestAttributes(request, response);
    } else {
        return null; // preserve the pre-bound RequestAttributes instance
    }
}</code>

initContextHolders

<code>private void initContextHolders(HttpServletRequest request,
        @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
}</code>

RequestContextHolder.setRequestAttributes

<code>public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
    if (attributes == null) {
        resetRequestAttributes();
    } else {
        if (inheritable) {
            inheritableRequestAttributesHolder.set(attributes);
            requestAttributesHolder.remove();
        } else {
            requestAttributesHolder.set(attributes);
            inheritableRequestAttributesHolder.remove();
        }
    }
}</code>

This method stores the

RequestAttributes

(which contain the

HttpServletRequest

and

HttpServletResponse

) into a

ThreadLocal

, making them safely accessible throughout the request handling thread.

Therefore, injecting

HttpServletRequest

or

HttpServletResponse

into a Spring MVC controller is safe and reliable.

End of analysis.

Backend DevelopmentSpringBootHttpServletRequestThreadLocalDynamic Proxy
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.