Saturday, August 23, 2014

How to Inject Objects Into Spring MVC Controller Using HandlerMethodArgumentResolver


This post shows how to implement HandlerMethodArgumentResolver in other to resolve and inject objects into Spring MVC controllers, but before we get to the actual implementation procedure, a little trivial overview of Spring MVC would be good.

Spring MVC is designed around the Front Controller Pattern which it uses to implement the Model View Controller pattern.

Since you are reading this post, I can rightly assume you already use Spring MVC and you know how it generally works: You register the DispatcherServlet -Spring MVC's Front Controller, write your controller class and map requests to controller methods using @RequestMapping, then put the necessary configuration in place which would enable Spring to pick the written controller class and have it as a spring managed bean.

A trivial controller class may look like this:

package com.springapp.mvc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
public class HelloController {


    @RequestMapping("/index")
    public String doGreeting(Model model) {
        model.addAttribute("greet", "Hello World");
        return "helloView";
    }
}

So when a request hits /index, doGreeting(...) method is invoked with an object of Model (the M in MVC) passed into it, which is populated with the greet property which can then be accessed in whatever view technology in used. The view is located using the "helloView" string returned by the controller.


So basically a request comes in, Spring does it's thing, looks through all the methods annotated with @RequestMapping, finds a match and then calls the corresponding method.

But something else goes on before the matched method is actually called; which is argument resolving.

When a web request comes in, Spring first needs to decide "which method am I going to invoke"? This decision is made based on rules provided by @RequestMapping. Once Spring figures this out, it would then look at the method signature to figure out, "ok, what do I need to inject into that method in other to be able to invoke it.

For example in the example above, the doGreeting(...) method has Model model as an argument. In other for that method to be correctly called, it needs to be supplied an object of type Model. Spring automagically handles this. And makes an object of type Model available.

You noticed we did not have to do any other thing, we just specified the needed argument in the method.

If in our controller we need the HttpServletRequest object, we can easily supply this also:

package com.springapp.mvc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class HelloController {


    @RequestMapping("/index")
    public String doGreeting(Model model, HttpServletRequest request ) {
        // do stuff with the request object
        model.addAttribute("greet", "Hello World");
        return "helloView";
    }
}


And voila, the HttpServletRequest would also be made available.

What then, if the controller depends on a class that is not an infrastructure related class but one that we write? Can we also add this as part of the argument of a controller method and have it automagically resolved by spring?

Let us say we have a Person class like this:

package com.springapp.mvc;


class Person {

    public String firstName;


    public Person(String firstName) {
        this.firstName = firstName;

    }

    public String getFirstname() {
        return firstname;
    }

}


and then in our controller method above we include a Person object as parameter:

package com.springapp.mvc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class HelloController {


    @RequestMapping("/index")
    public String doGreeting(Model model, 
                             HttpServletRequest request,
                             Person person ) {
        // do stuff with the request object
        model.addAttribute("greet", person.getFirstName() + ", Hello World");
        return "helloView";
    }
}

Would this work as it did with HttpServletRequest?

The answer is no. Spring can not automatically understand what Person object is, instantiate a copy, populates its properties and make it available as method argument for doGreating(...). The reason HttpServletRequest could be automatically made available as an argument is because it is part of the objects Spring can automatically resolve and inject. A list of  such objects includes:

HttpServletRequest
Principal
Locale
InputStream
Reader
HttpServletResponse
OutputStream
Writer
HttpSession

So how then do we get Spring to resolve custom objects to be injected into controller methods as arguments? This is where HandlerMethodArgumentResolver comes into play.

HandlerMethodArgumentResolver is an interface to which you provide an implementation which contains logic that would then be used to resolve method parameters into argument values in the context of a given request: sort of like an Object factory.

The interface defines two methods:

supportsParameter(MethodParameter parameter)
and
resolveArgument(MethodParameter parameter, 
                           ModelAndViewContainer mavContainer, 
  NativeWebRequest webRequest, 
  WebDataBinderFactory binderFactory)

As explained before, when a request comes in and a matched method of a controller is found, Spring proceeds to call the matched controller method by first checking the arguments of the method, if the specified arguments are part of the objects it can automatically resolve, it does the resolving. If it sees objects it can not automatically resolve, it then checks if there are any registered beans that implements the HandlerMethodArgumentResolver interface which can handle the resolving of the argument.

The supportsParameter is where you provide logic that determines which argument type your custom HandlerMethodArgumentResolver can handle. It returns a Boolean to indicate this. While resolveArgument does the actually object instantiating. It returns the object to be injected.

Let's see this in action by implementing an HandlerMethodArgumentResolver that resolves our Person Object.


package com.springapp.mvc;

package com.springapp.mvc;

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 class PersonResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {

        return parameter.getParameterType().equals(Person.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, 
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, 
                                  WebDataBinderFactory binderFactory)
    throws Exception {

        return new Person("Akpos");
    }
}


Once the PersonResolver is written, it needs to be configured as a Method argument resolver. If using XML for configuration, this is better achieved by using the <mvc:argument-resolvers> within the <mvc:annotation-driven/> namespace:


        
            
        


A JavaConfig versin of this configuration would look thus:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addArgumentResolvers(List< Handlermethodargumentresolver > argumentResolvers) {
        PersonResolver personResolver = new PersonResolver();
        argumentResolvers.add(personResolver);
  }
}
...

Once the configuration is done, the PersonResolver is ready to go and would make sure that a Person object is always made available to any controller method that has it has an argument.

PersonResolver above is a simple implementation of HandlerMethodArgumentResolver. The supportsParameter method uses the getParameterType(...) method of MethodParameter to check if the custom object needed for controller method argument is of type Person. Returns true if it is.

If and only if supportsParameter returns true, that is when resolveArgument(...) method is called. And this just returns a Person Object.

A more involved HanderMethodArgumentResolver can be written using a combination of the various functionality exposed by MethodParameter.

For example a controller method argument can be resolved and injected based on annotations. This can be done using hasParameterAnnotation(...) method of MethodParameter which checks if parameter has a given annotation.

The arguments supplied in resolveArgument(...) also allows you to do more complex and involved operations. For example the HttpServletClass can be retrieved for use from NativeWebRequest while ModelAndViewContainer can be used to record model and view related decisions made within resolveArgument(...).

The general idea is that the process of resolving method arguments for controller in Spring MVC, using HanderMethodArgumentResolver is quite flexible and plug-gable, it allows for almost any imaginable scenerio.

For historical purposes it should be noted that prior to Spring 3.1, the way to have achieved method argument resolving would have been to implement the WebArgumentResolver interface. but since Spring 3.1, implementing HandlerMethodArgumentResolveris the preferred way now. For more information on this, see New Support Classes for @RequestMapping methods in Spring MVC 3.1

3 comments:

Onslow the Gardener said...

Maybe adding @ModelAttribute before Person person?

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args

Unknown said...

Thanks for this entry.

dade said...

@somo I wrote another post in which I took a closer look at @ModelAttribute, its usage and how it compares to HandlerMethodArgumentResolver. You can check it out here http://geekabyte.blogspot.nl/2014/10/injecting-objects-into-spring-mvc.html