Which Spring MVC Extension Points Parse Parameters and Handle Return Values?
This article explains the purpose and usage of Spring MVC’s HandlerMethodArgumentResolver and HandlerMethodReturnValueHandler extension points, demonstrates how to implement custom argument resolvers and return‑value handlers, discusses common pitfalls such as handler ordering, and shows how to replace the default processors to achieve unified request and response processing.
Overview
Spring MVC is a flexible framework that allows deep customization through two important extension points: HandlerMethodArgumentResolver for parsing controller method parameters and HandlerMethodReturnValueHandler for processing return values. The article explores their roles, typical scenarios, and how to implement them.
HandlerMethodArgumentResolver
The interface converts HTTP request data into method arguments. Spring MVC provides built‑in resolvers for annotations such as @RequestParam, @PathVariable, and @RequestBody. When a request arrives, Spring iterates the registered resolvers, calls #supportsParameter() to find a matching one, and then invokes #resolveArgument().
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception;
}Example scenario: instead of passing userId from the client (which poses security risks), a custom @LoginUser annotation can be used to inject the current logged‑in user from the request context.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {}Custom resolver implementation:
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginUser.class)
&& methodParameter.getParameterType().equals(UserSession.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory binderFactory) {
return getCurrentUser(nativeWebRequest);
}
private UserSession getCurrentUser(NativeWebRequest webRequest) {
// Simulated user session for demo
UserSession session = new UserSession();
session.setId(8L);
session.setName("张三");
session.setOrgId(6L);
return session;
}
}Register the resolver via WebMvcConfigurer#addArgumentResolvers:
@Configuration
public class DefaultWebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginUserArgumentResolver());
}
}After registration, a controller method can receive the injected user session:
@GetMapping("/loginUser")
public User getUser(@RequestParam("id") Long id, @LoginUser UserSession session) {
Long userId = session.getId();
return userService.getUser(userId);
}Postman test shows the resolver correctly provides userId = 8 and the service returns the expected user.
HandlerMethodReturnValueHandler
After a controller method finishes, Spring MVC iterates all registered HandlerMethodReturnValueHandler instances, selects the first that supports the return type, and calls its #handleReturnValue() method.
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}A common need is to wrap every response in a standard JSON structure ( ResponseVO). A custom return‑value handler can achieve this:
public class ResponseReturnValueHandler implements HandlerMethodReturnValueHandler {
private final HandlerMethodReturnValueHandler delegate;
public ResponseReturnValueHandler(HandlerMethodReturnValueHandler delegate) {
this.delegate = delegate;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return delegate.supportsReturnType(returnType);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (!(returnValue instanceof ResponseVO)) {
returnValue = ResponseVO.success(returnValue);
}
delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}Replace the default RequestResponseBodyMethodProcessor with the custom handler during application startup:
@Configuration
public class RequestArgumentAndReturnValueConfig implements InitializingBean {
@Resource
RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Override
public void afterPropertiesSet() throws Exception {
List<HandlerMethodReturnValueHandler> origin = requestMappingHandlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(origin.size());
for (HandlerMethodReturnValueHandler handler : origin) {
if (handler instanceof RequestResponseBodyMethodProcessor) {
newHandlers.add(new ResponseReturnValueHandler(handler));
} else {
newHandlers.add(handler);
}
}
requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
}
}After restarting the project, the API now returns a unified JSON response:
{
"code": 200,
"msg": "OK",
"data": {
"id": 8,
"userNo": "001",
"gender": 0,
"name": "张三",
"birthday": "2024-08-07",
"phone": "12234",
"isDelete": 0,
"createTime": "2024-07-03T16:09:12"
}
}Implementation Details
The request flow enters DispatcherServlet#doDispatch(), then RequestMappingHandlerAdapter invokes the controller method via ServletInvocableHandlerMethod#invokeAndHandle(). Parameter resolution happens in HandlerMethodArgumentResolverComposite, which iterates the resolver list, checks #supportsParameter(), and calls #resolveArgument(). Return‑value handling follows a similar pattern with HandlerMethodReturnValueHandlerComposite, selecting a handler via #supportsReturnType() and invoking #handleReturnValue(). Both extension points share almost identical processing pipelines.
Conclusion
HandlerMethodArgumentResolverand HandlerMethodReturnValueHandler are crucial Spring MVC extension points that enable developers to customize parameter parsing and response handling without modifying the core framework. Proper use of these interfaces allows flexible implementation of complex business requirements such as authentication injection, response wrapping, encryption, and more.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
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.
