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.
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
requestobject is actually a proxy instance of
ObjectFactoryDelegatingInvocationHandler. The handler delegates to a
RequestObjectFactorywhich creates the real
HttpServletRequestobject.
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
HttpServletRequestis stored in a
ThreadLocalvariable, which is bound to the current thread. The following trace shows how the request object is placed into this
ThreadLocalduring the servlet processing lifecycle.
Servlet processing flow
Request →
service()→
doXXX()Spring MVC’s core controller
DispatcherServletextends
FrameworkServlet. Each
doXXXmethod 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
ServletRequestAttributesobject:
<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
HttpServletRequestand
HttpServletResponse) into a
ThreadLocal, making them safely accessible throughout the request handling thread.
Therefore, injecting
HttpServletRequestor
HttpServletResponseinto a Spring MVC controller is safe and reliable.
End of analysis.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.