Walk through what RequestMappingHandlerAdapter.invokeHandlerMethod does when a @RequestMapping method is called.
answer
- HandlerMethod → ServletInvocableHandlerMethod
- inject arg-resolver + return-handler composites
- ModelAndViewContainer threads through
- invokeAndHandle: resolve args, reflect-invoke, handle return
- async started → return null, re-dispatch later
basics
~20 sIt wraps the controller method in a ServletInvocableHandlerMethod, plugs in the argument resolvers and return-value handlers, binds a ModelAndViewContainer, resolves each parameter, invokes the method, and hands the return value to the right return-value handler.
solid answer
~30 sWhen RequestMappingHandlerAdapter.handleInternal runs, it calls invokeHandlerMethod. There it builds a ServletInvocableHandlerMethod from the HandlerMethod and injects the shared HandlerMethodArgumentResolverComposite and HandlerMethodReturnValueHandlerComposite, plus a WebDataBinderFactory and a ModelFactory. It creates a ModelAndViewContainer, initializes model attributes (@ModelAttribute methods, @SessionAttributes), then calls invokeAndHandle. invokeAndHandle resolves every method argument by asking each argument resolver supportsParameter/resolveArgument, reflectively invokes the controller method, and passes the return value to the first return-value handler whose supportsReturnType is true (e.g. RequestResponseBodyMethodProcessor for @ResponseBody). Finally getModelAndView turns the ModelAndViewContainer into a ModelAndView (or null if the request was marked handled). Async returns (Callable, DeferredResult) are detected and delegated to WebAsyncManager.
code
java · 25 lines// Paraphrase of RequestMappingHandlerAdapter.invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocable = createInvocableHandlerMethod(handlerMethod);
invocable.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocable.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocable.setDataBinderFactory(binderFactory);
invocable.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
modelFactory.initModel(webRequest, mavContainer, invocable);
// ...async (WebAsyncManager) wiring omitted...
invocable.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null; // will be re-dispatched
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}go deeper
Know invokeHandlerMethod is where the controller method actually gets called, with parameters filled in automatically.
Trace the wrapping into ServletInvocableHandlerMethod, injection of the composites, and invokeAndHandle resolving args then handling the return.
Discuss ModelAndViewContainer threading, requestHandled semantics, and how the null ModelAndView flows back to DispatcherServlet.
Cover async short-circuit via WebAsyncManager, provided-args injection, and the performance rationale of shared stateless composites.
## Where this sits `RequestMappingHandlerAdapter` is the `HandlerAdapter` for annotated controller methods. Its `handle(...)` → `handleInternal(...)` → **`invokeHandlerMethod(request, response, handlerMethod)`** is where a single `@RequestMapping` method actually gets executed. Everything about how parameters get filled and how return values become responses is wired here. ## Key collaborators - **`HandlerMethod`** — an immutable descriptor of the controller bean + `java.lang.reflect.Method` (produced by `HandlerMapping`). - **`ServletInvocableHandlerMethod`** — an *invocable* extension of `HandlerMethod` that can (a) resolve arguments, (b) call the method, and (c) handle the return value. This is the workhorse object. - **`HandlerMethodArgumentResolverComposite`** — the ordered list of `HandlerMethodArgumentResolver`s (for `@RequestParam`, `@PathVariable`, `@RequestBody`, `Model`, `Pageable`, etc.). - **`HandlerMethodReturnValueHandlerComposite`** — the ordered list of `HandlerMethodReturnValueHandler`s (for `@ResponseBody`, `ModelAndView`, `String` view names, `ResponseEntity`, etc.). - **`WebDataBinderFactory`** / **`ModelFactory`** — create `WebDataBinder`s (validation/conversion) and populate the model from `@ModelAttribute` methods and `@SessionAttributes`. - **`ModelAndViewContainer`** — a mutable holder passed through the whole invocation, accumulating model attributes and a view/redirect flag, plus a `requestHandled` flag. ## Step by step (invokeHandlerMethod) 1. Wrap the request: `ServletWebRequest webRequest = new ServletWebRequest(request, response)`. 2. `WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod)` — aggregates `@InitBinder` methods. 3. `ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory)`. 4. Build the invocable: `ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod)` and then **inject** the shared components: - `setHandlerMethodArgumentResolvers(this.argumentResolvers)` - `setHandlerMethodReturnValueHandlers(this.returnValueHandlers)` - `setDataBinderFactory(binderFactory)` - `setParameterNameDiscoverer(this.parameterNameDiscoverer)` 5. Create `ModelAndViewContainer mavContainer` and `modelFactory.initModel(...)` — runs `@ModelAttribute` methods, merges `@SessionAttributes`. 6. Set up async support: obtain the `WebAsyncManager`, configure `AsyncTaskExecutor` and `CallableInterceptor`s. If a concurrent result already exists (async re-dispatch), the invocable is wrapped so only the return-value handling re-runs. 7. **`invocableMethod.invokeAndHandle(webRequest, mavContainer)`** — the core call (see below). 8. If async started (`asyncManager.isConcurrentHandlingStarted()`) return `null` immediately — the request will be re-dispatched later. 9. Otherwise `return getModelAndView(mavContainer, modelFactory, webRequest)` — converts the container to a `ModelAndView`, or returns `null` if `mavContainer.isRequestHandled()` (e.g. `@ResponseBody`). ## Inside invokeAndHandle (ServletInvocableHandlerMethod) ``` Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // invokeForRequest -> getMethodArgumentValues(...) -> for each MethodParameter: // argumentResolvers.supportsParameter(p) ? resolveArgument(p, mavContainer, req, binderFactory) // then doInvoke(args) via reflection on the controller bean setResponseStatus(webRequest); // honors @ResponseStatus if (returnValue == null) { ...mark handled if not modified/status set...; return; } mavContainer.setRequestHandled(false); returnValueHandlers.handleReturnValue(returnValue, returnType, mavContainer, webRequest); ``` The return value is dispatched to the **first** `HandlerMethodReturnValueHandler` whose `supportsReturnType(returnType)` is true. For `@ResponseBody`/`@RestController` that is `RequestResponseBodyMethodProcessor`, which runs `HttpMessageConverter`s to serialize the body and sets `mavContainer.setRequestHandled(true)` so no view rendering happens. ## Gotchas - The **same composite instances** are shared across every request; only the lightweight `ServletInvocableHandlerMethod` wrapper is created per invocation — cheap. - If a parameter has no supporting resolver, you get `IllegalStateException: No suitable resolver`. If a return type has no handler, `IllegalArgumentException: Unknown return value type`. - `providedArgs` lets Spring inject already-known values (e.g. a caught exception into an `@ExceptionHandler` method). - `requestHandled == true` (set by `@ResponseBody`) is exactly why `getModelAndView` returns `null` — tying back to why `HandlerAdapter.handle` can legitimately return `null`. ## When to care Understanding this explains: why `@RequestParam`/`@RequestBody` 'just work', how to add a custom argument type, why `@ResponseBody` skips view resolution, and how async return types short-circuit the flow.
- Why does invokeHandlerMethod create a new ServletInvocableHandlerMethod per request but reuse the argument-resolver and return-value-handler lists?The composites are stateless and expensive to build, so they are configured once and shared. The ServletInvocableHandlerMethod is a thin, request-scoped wrapper that binds those shared components to one specific HandlerMethod + WebDataBinderFactory for a single invocation.
- What is the role of ModelAndViewContainer?It is a mutable holder passed through argument resolution, invocation, and return-value handling. It accumulates model attributes and the view/redirect decision, and its requestHandled flag tells getModelAndView whether to produce a ModelAndView (view path) or return null (response already written, e.g. @ResponseBody).
saying these in an interview costs you the question
- Thinking a fresh set of argument resolvers/return handlers is built per request (only the wrapper is).
- Believing @RequestBody deserialization happens in the adapter itself rather than in a HandlerMethodArgumentResolver (RequestResponseBodyMethodProcessor).
- Assuming the method return value always becomes a view name.