A few weeks ago we learned to transform the value of a single request parameter into an object, which is passed to our controller method as a method parameter, by using Spring type converters.
Using type converters is a good choice if we want to create "simple" value objects and inject these objects into our controller methods.
However, sometimes we want to inject objects that fulfill these requirements:
- The injected object has more than one field and the values of these fields must be read from different sources.
- The injected object is not a form object.
- The injected object is not read from the request body.
If we want to fulfill these requirements, we must create a custom HandlerMethodArgumentResolver, and this blog post describes how we can do it.
Let's start by creating a custom HandlerMethodArgumentResolver that can create FooBar objects.
Creating a Custom HandlerMethodArgumentResolver
The FooBar class is a simple class that has two final fields: bar and foo. Its source code looks as follows:
public class FooBar { private final String bar; private final String foo; FooBar(String bar, String foo) { this.bar = bar; this.foo = foo; } public String getBar() { return bar; } public String getFoo() { return foo; } }
We can create a custom HandlerMethodArgumentResolver class, which can create FooBar objects, by following these steps:
- Implement the HandlerMethodArgumentResolver interface.
- Implement the supportsParameter(MethodParameter methodParameter) method. This method must return true if the type of the method parameter is FooBar and false otherwise.
- Implement the resolveArgument() method by following these steps:
- Get the value of the 'bar' request parameter.
- Get the value of the 'foo' request parameter.
- If the 'bar' request parameter is not found from the request, use the default value.
- If the 'foo' request parameter is not found from the request, use the default value.
- Create a new FooBar object and return the created object.
The source code of the FooBarHandlerMethodArgumentResolver class looks as follows:
import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; public final class FooBarHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterType().equals(FooBar.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { String bar = nativeWebRequest.getParameter("bar"); String foo = nativeWebRequest.getParameter("foo"); if (isNotSet(bar)) { bar = "defaultBar"; } if (isNotSet(foo)) { foo = "defaultFoo"; } return new FooBar(bar, foo); } private boolean isNotSet(String value) { return value == null; } }
Additional Reading:
Before we can use our new HandlerMethodArgumentResolver class, we have to configure the application context of our web application.
Configuring the Application Context of Our Web Application
Before we can inject FooBar objects into our controller methods, we have to register the FooBarHandlerMethodArgumentResolver as a method argument resolver. This section describes how we can configure the application context of our web application that uses either Spring or Spring Boot.
Configuring a Spring Web Application
We can register the FoobarHandlerMethodArgument class as a method argument resolver by making the following changes to the configuration class that configures the web layer of our Spring web application:
- Extend the WebMvcConfigurerAdapter class.
- Override the addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) method of the WebMvcConfigurerAdapter class.
- Register a custom method argument resolver by creating a new FooBarHandlerMethodArgumentResolver object and adding the creating object to the List given as a method parameter.
The source code of the WebMvcContext class looks as follows:
import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; @Configuration @EnableWebMvc class WebMvcContext extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new FooBarHandlerMethodArgumentResolver()); } }
Let's move on and find out how we can configure the application context of our Spring Boot web application.
Configuring a Spring Boot Web Application
We can register the FoobarHandlerMethodArgument class as a method argument resolver by making the following changes to the "application class" of our Spring Boot web application:
- Extend the WebMvcConfigurerAdapter class.
- Override the addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) method of the WebMvcConfigurerAdapter class.
- Register a custom method argument resolver by creating a new FooBarHandlerMethodArgumentResolver object and adding the creating object to the List given as a method parameter.
The source code of the SpringBootExampleApplication class looks as follows:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; @Configuration @EnableAutoConfiguration @ComponentScan public class SpringBootExampleApplication extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new FooBarHandlerMethodArgumentResolver()); } public static void main(String[] args) { SpringApplication.run(SpringBootExampleApplication.class, args); } }
We are done. Let's find out how we can use our new method argument resolver.
Using the Custom HandlerMethodArgumentResolver
After we have configured the application context of our web application, we can simply add a new FooBar method parameter into our controller method. The source code of our controller class looks as follows:
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController final class FooBarController { @RequestMapping(value = "/test", method = RequestMethod.GET) public void processFooBar(FooBar fooBar) { //Do stuff } }
We can now set the information of the FooBar object by setting the values of the following request parameters:
- The value of the 'bar' request parameter is set as the value of the bar field.
- The value of the 'foo' request parameter is set as the value of the foo field.
The following examples demonstrate how we can create new FooBar object, set the values of its bar and foo fields, and inject it into our controller method.
Example 1:
We have to create a FooBar object that fulfills the following requirements:
- The value of the bar field is 'bar'.
- The value of the foo field is 'foo'.
We can create this object by sending a GET request to the following url: '/test?bar=bar&foo=foo'.
Example 2:
If we want to create a FooBar and set the default values into its bar and foo fields, we have to send a GET request to the following url: '/test'.
Let's move on and summarize what we learned from this blog post.
Summary
This blog post has taught us three things:
- If we want to inject "complex" objects into our controller methods, and these objects aren't form objects or read from the request body, we should create a custom method argument resolver.
- We can create a new method argument resolver by implementing the HandlerMethodArgumentResolver interface.
- Before we can use a custom HandlerMethodArgumentResolver, we have to register it as a method argument resolver.
P.S. You can get the example application of this blog post from Github.
I was looking into part of springframewok source code, and I was asking how to register my own Method argument resolver.
Your post give me the answer in a simple, yet clear and comprehensive way!(since web and boot configs)
Thanks.
You are welcome. I am happy to hear that this blog post was useful to you.
Hello, it's a very good example but what i want to ask is what if i try to inject (i.e a service class with @autowired anno) a field into my custom argument resolver?
You need to follow these steps:
First, annotate your custom argument resolver class with the
@Component
annotation and ensure that the Spring container scans the correct package.Second, inject the service into the custom argument resolver bean.
Third, inject your custom argument resolver bean into your configuration class by using field injection:
Fourth, register a custom method argument resolver in your configuration class:
Hi Petri,
I'm using a slightly different approach. I extend the HandlerMethodArgumentResolver without adding methods and implement my own interface. Then you are able to autowire a list of custom argument resolvers and add them in a loop. That makes it a lot easier to add additional argument resolvers because you only have to implement the interface and add @Component to your class.
I'm using this approach also for converters etc.
Regards,
Patrick
Hi Patrick,
That sounds interesting. I will try your approach the next time when I have to do this. Thank you for sharing!
Very helpful. Thanks for posting.
You are welcome.
Hi petri, thanks for sharing , I have issues using a custom HandlerMethodReturnValueHandler, some how I can't I get it to work for a whole day. Can you write on this ?.
Cheers
Hi,
Thank you for the suggestion. I have added this blog post on my to-do list, and I will write it after I have finished the next part of my JUnit 5 tutorial.
implement WebMvcConfigurer instead WebMvcConfigurerAdapter to get
'addResolvers...'
Hi, i have a question. What if i don't very FooBar object sending me defaultBar, defaultFoo.
What if I want to have one '/test1' whos sending me back defaultBar, defaultFoo, and second something else. What I'm tryin' to say that I want to get those parameters only by calling 'test1'.And how I can do it.
Best Regards.
Hi,
Do you mean that you want to pass the
foo
andbar
strings to your controller method as method parameters? If so, you should use either the@RequestParam
annotation or the@PathVariable
annotation.