Monday, October 06, 2014

Injecting Objects into Spring MVC Controller Using @ModelAttribute Annotation

In How to Inject Objects Into Spring MVC Controller Using HandlerMethodArgumentResolver, I showed how to implement the HandlerMethodArgumentResolver interface in other to have a custom object passed as a method argument of a Spring MVC controller.

This post looks at how to still pass objects into the Spring MVC controller, but using a some what different approach: The @ModelAttribute annotation.

The @ModelAttribute annotation can be used to designates operation that can both retrieve and put stuff into the model. Its functionality depends on where it is placed...two places actually: On controller methods and on controller's method arguments.

When @ModelAttribute is placed on a controller's method, it allows the putting of stuff into the Model and when on a controller method arguments, it allows retrieving stuff from the Model...

I thus, like to think of @ModelAttribute as an annotation driven mechanism for writing and reading from the Model.

This post would look at these two methods of using @ModelAttribute and how it can be used to inject objects into controller's methods.



We would be using a Person object for illustration:

class Person {
 
    private String firstName;
    private String lastName;

    public Person() {
    }
 
    public void setFirstName(String fname) {
        this.firstName = fname;
    }

    public String getFirstname() {
        return firstName;
    }

    public void setLastName(String lname) {
        this.lastName = lname;
    }

    public String getLastName() {
        return lastName;
    }
 
}

With that articulated, Let's see how this @ModelAttribute thing works.

1. Using @ModelAttribute on Controller Methods: Aka Write To Model.

If you have a method in a controller and it is annotated with @ModelAttribute, the returned value of that method would be added to the model. 


For example:

@Controller
@RequestMapping("/")
public class HelloController {

@ModelAttribute
public Person putPersonInModel() {
      Person person = new Person();
      person.setFirstName("Akpos");
      person.setLastName("Merenge");
      return person; // person would be added to model
}
}

The return type of putPersonInModel method is Person, thus a Person object would be added to the Model under the name "person" since the default model attribute for the entry added this way is the camel-cased version of the return type. If the returned type was SuperHero, the Person object would be added to the Model with the “superHero” entry.

Some quick things to note about using @ModelAttribute this way.

1. Controller methods annotated with @ModelAttribute are always called first before the actual request handling methods are called (i.e. before method annotated with @RequestMapping)

2. You can specify the actual modal key under which the returned type would be added to the model, overriding the default. This is done using the value property of the @ModelAttribute. For example to have Person added to the Model using “user” key, you do

@ModelAttribute(“user”)
public Person putPersonInModel() {}

3. A controller can contain any amount of methods annotated with @ModelAttribute.

4. A method annotated with @ModelAttribute can also have Spring inject all the Infrastructure objects that can be injected into methods annotated with @RequestMapping (i.e. the request handling methods) so as you can have Locale, Model, Reader, HttpServletRequest, HttpServletResponse e.t.c Not just that, methods annotated with @ModelAttribute also supports all handler mapping annotations like @RequestParam This allows the ability to do stuff like manually add multiple entries into the model with one @ModelAttribute annotated method. For example:

@ModelAttribute(“user”)
public void putMultipleStuffInModel(Model model) {
       model.addAttribute(“firstEntry”, new Person());
       model.addAttribute(“secondEntry, new Admin());
       // and many more
}
}

Notice that with this method, the return type is void.

This ability to have infrastructure objects injected into methods annotated with @ModelAttribute can also allow you to do cool stuff like adding the required custom object to the Model based on non trivial logic using the infrastructure objects made available...

For example you can add different stuff to the model depending on the locale of the user...

Or use @RequestParam to retrieve, from the database, the object to be added to the Model

@ModelAttribute
public Person putPersonInModel(@RequestParam(“id”) int personId) {
      Person person = new PersonRepository.loadByPersonId(personId);
      return person;
}

...and that is the part to writing to the Model using @ModelAttribute. Lets take a look at the retrieving part...

2. Using @ModelAttribute on Arguments of Controller Methods: Aka Read From Model.

If you have a request handling method, you can directly retrieve an object existing in the Model and have it used as part of the method's argument. For example having had a Person Object added to the Model via the @ModelAttribute annotated method, putPersonInModel above, you can retrieve the Person object thus:

@RequestMapping(value="/", method = RequestMethod.GET)
public String printWelcome(ModelMap model,
                            @ModelAttribute("person") Person person) {
        // do stuff with the Person Object
}

For what happens if the @ModelAttribute annotation is omitted, read Injecting and Binding Objects to Spring MVC Controllers

And that is basically how you retrieve objects from the Model using @ModelAttribute and have it injected into Spring MVCs controller methods.

@ModelAttribute versus Using HandlerMethodArgumentResolver

The idea for this post came about when I had a comment on How to Inject Objects Into Spring MVC Controller Using HandlerMethodArgumentResolver suggesting if usage of @ModelAttribute won't actually achieve the same thing...

Answer to that? Sort of but... HandlerMethodArgumentResolver is more flexible and dexterous when compared to @ModelAttribute, majorly due to the fact that the logic for object resolving and injection can be shared across controllers, with @ModelAttribute, your logic would need to be part of the controller it would work on.

So which to use? well, like most things, it depends :) as the use case would judge which would be the most pragmatic approach.

No comments: