Last Update: 06.08.2018. By codeboje in Spring MVC | Spring Boot
In this tutorial, we will discuss HandlerMethodArgumentResolver which is being used to resolve the arguments of the public MVC methods (handler methods). To understand this feature in depth, we will look at different out of the box method argument resolvers and how we can a write custom argument resolver. We will take an example of resolving the hostname (domain) of the server.
As its name suggests, this is a feature of resolving the arguments of public MVC methods. If you are a Spring Web MVC or Rest application developer, then knowingly or unknowingly you must have used this powerful feature.
To begin with, let’s have a look at below code snippet.
@RequestMapping("/users/{id}")
public String sayHello(HttpServletRequest httpRequest, @PathVariable("id") String userId) {
// We will get the httpRequest object
// userId argument will have the value passed in place of {id} in URL
return "Hello " + userId;
}
In the above code, there are two arguments in the sayHello method.
Have we done anything in order to resolve these arguments from the actual request?
No, instead these arguments are directly available in the handler method to use. Argument “userId” is auto-populated with the value of {id} passed in Request Url.
HandlerMethodArgumentResolver
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
Abstract Method Details
supportsParameters- Implementation of this method decides whether this HandlerMethodArgumentResolver should resolve the argument or not. An argument to this method is MethodParameter which could be used to check the method name, argument type, annotations etc and based on the values implementation can be coded.
Note Spring does some caching and supportsParameters is not called for same request every time.*
resolveArgument- This method is executed only if supportParameters method returns true. Implementation of this method has a responsibility to create and return the object of the requested type.
Spring provides several implementations catering to different scenarios. Below are some of the implementations which we use quite often.
Now we have got a good understanding of how the arguments of public MVC methods are resolved and different argument resolvers used by Spring out of the box. Now it is the time to discuss “When to use HandlerMethodArgumentResolver“
Quite often we get into a situation where HandlerMethodArgumentResolver would be a perfect choice. For example, if we have a requirement to combine the data from multiple sources like request, session or database and construct an argument of a public MVC method.
You might be thinking what is big deal in it and we could construct it by pulling data from required sources and construct in handler method itself. Yes, you are correct and we could do this but this is not a correct approach and design as
@RequestMapping("/users/name")
public String getUserName(HttpServletRequest httpRequest) {
User user = new User();
// get id from request
user.setId(httpRequest.getParameter("id"));
// get name from database service based on id
user.setName(UserDataService.getName(user.getId()));
return user.getName();
}
This will help us having clean code along with the advantages listed above.
@RequestMapping("/users/name")
public String getUserName(User user) {
return user.getName();
}
Hint If you have a requirement which demands a custom and complex object to be constructed from the multiple sources like request, session or any backend service and should be available under handler method, then writing custom handler method argument resolver is a perfect choice.
Now it is the time to write some code and see the concepts we learned practically. As mentioned in the introduction, we will write an example of resolving the hostname of the server using Spring Boot 2 project. The tutorial will be linked to a GitHub repo later.
Example Overview
We are going to write an example where the requirement is to return the hostname of the server to which the request was sent. To have a better understanding, we will implement this example with and without using HandlerMethodArgumentResolver.
Hint We can get the server host name using getServerName() method of HttpServletRequest argument
@Controller
public class GetDomainController {
@GetMapping("/domain")
public @ResponseBody String getDomain(HttpServletRequest httpRequest) {
String domain = httpRequest.getServerName();
return domain;
}
}
Code Explanation
Note We can use @RestController in place of @Controller and remove @ResponseBody annotation*
Step 1 - Write Handler Mapping
@Controller
public class GetDomainController {
@GetMapping("/domain")
public @ResponseBody String getDomain(String domain) {
return domain;
}
}
Code Explanation
In this approach, the method argument has been changed to String in place of HttpServletRequest. Value of this domain argument will be populated via custom HandlerMethodArgumentResolver implementation that we will be writing next.
Step 2- Writing Custom HandlerMethodArgumentResolver implementation
public class DomainArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelViewContainer, NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
String domain = ((HttpServletRequest) nativeWebRequest.getNativeRequest()).getServerName();
return domain;
}
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(String.class);
}
}
Code Explanation
Step 3 - Registering DomainArgumentResolver in order to get it activated.
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new DomainArgumentResolver());
}
}
Code Explanation
Note Prior to Spring 5, we need to extend WebMvcConfigurerAdapter class and override the addArgumentResolvers method but with Spring 5, WebMvcConfigurerAdapter class has been deprecated. If you are using Spring 5, the recommendation is to implement WebMvcConfigurer as we have done in this example.
Other implementation of supportsParameter method
We can say that implementation of supportsParameter method above is not efficient as this is going to return true for all the handler methods which have an argument of type String and value of that argument will be populated as a hostname. Like if we have another handler mapping as below, name argument will also be populated as a hostname.
@GetMapping("/name")
public String getName(String name){
return name;
}
To overcome, this issue, we can improve the implementation by using other parameters like a method name, request Url etc available from the methodParameter argument and can create multiple argument resolver if need be.
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
if(! methodParameter.hasMethodAnnotation(GetMapping.class)){
return false;
}
GetMapping annotation = (GetMapping) methodParameter.getMethodAnnotation(GetMapping.class);
String[] paths = annotation.value();
Predicate<String> predicate = path -> path.equals("/domain");
return Arrays.stream(paths).anyMatch(predicate);
}
From the above discussed different approaches of resolve method arguments of public MVC methods using a simple example, it is clear that how using HandlerMethodArgumentResolver implementation helps us in writing maintainable, manageable and easy to debug code. Also if you see, Approach 1 is also using HandlerMethodArgumentResolver behind the scene in order to pass HttpServletRequest argument but it was done by Spring out of the box ArgumentResolvers.