Wednesday, 2 November 2011

Using Spring MVC’s @ModelAttribute Annotation

The @ModelAttribute annotation is used as part of a Spring MVC web app and can be used in two scenarios.
  • Firstly, it can be used to inject data objects the model before a JSP loads. This makes it particularly useful in ensuring that a JSP has all the data is needs to display itself. The injection is achieved by binding a method return value to the model.
  • Secondly, it can be used to read data from an existing model assigning it to handler method parameters.

To demonstrate the @ModelAttributes, I'm using the simplest of scenarios: adding a user account to a hypothetical system and then, once the user account has been created, displaying the new user’s details on a welcome screen.

In order to start the ball rolling, I’ll need a simple User bean with some familiar fields: first name, last name, nick name and email address - the usual suspects.

public class User {

 
private String firstName;

 
private String lastName;

 
private String nickName;

 
private String emailAddress;

 
public String getFirstName() {
   
return firstName;
 
}

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

 
public String getLastName() {
   
return lastName;
 
}

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

 
public String getNickName() {
   
return nickName;
 
}

 
public void setNickName(String nickName) {
   
this.nickName = nickName;
 
}

 
public String getEmailAddress() {
   
return emailAddress;
 
}

 
public void setEmailAddress(String emailAddress) {
   
this.emailAddress = emailAddress;
 
}
}

I’ll also need a Spring MVC controller to handle creating users. This will contains a couple of important methods that use the @ModelAttribute annotation, demonstrating the functionality outlined above.

The method below demonstrates how to bind a method return value to a model.

  /**
   * This creates a new User object for the empty form and stuffs it into
   * the model
   */
 
@ModelAttribute("User")
 
public User populateUser() {
   
User user = new User();
    user.setFirstName
("your first name");
    user.setLastName
("your last name");
   
return user;
 
}

This method is called before every @RequestMapping annotated handler method to add an initial object to the model, which is then pushed through to the JSP. Notice the word every in the above sentence. The @ModelAttribute annotated methods (and you can have more than one per controller) get called irrespective of whether or not the handler method or JSP uses the data. In this example, the second request handler method call doesn’t need the new user in the model and so the call is superfluous. Bare in mind that this could possibly degrade application performance by making unnecessary database calls etc. It’s therefore advisable to use this technique only when each handler call in your Controller class needs the same common information adding to the model for every page request. In this example, it would be more efficient to write:

  /**
   * Create the initial blank form
   */
 
@RequestMapping(value = PATH, method = RequestMethod.GET)
 
public String createForm() {

   
populateUser();
   
return FORM_VIEW;
 
}

The method below demonstrates how to annotate a request method argument, so that data is extracted from the model and bound to the argument.

  /**
   * This is the handler method. Stick the user bean into a new attribute for
   * display on the next page
   *
   *
@param user
   *            The user bean taken straight from the model
   *
@param model
   *            An out param. Takes the user and adds it to the model FOR the
   *            NEXT page under a different name.
   *
   */
 
@RequestMapping(value = PATH, method = RequestMethod.POST)
 
public String addUser(@ModelAttribute("user") User user,
      BindingResult result, Model model
) {

   
model.addAttribute("newUser", user);
   
return WELCOME_VIEW;
 
}

In this example, an ‘add user’ button on a form has been pressed calling the addUser() method. The addUser() method needs a User object from the incoming model, so that the new user’s details can be added to the database. The @ModelAttribute("user") annotation applied takes any matching object from the model with the “user” annotation and plugs it into the User user method argument

Just for the record, this is the full controller code.

@Controller
public class AddUserController {

 
private static final String FORM_VIEW = "adduser.page";

 
private static final String WELCOME_VIEW = "newuser.page";

 
private static final String PATH = "/adduser";

 
/**
   * Create the initial blank form
   */
 
@RequestMapping(value = PATH, method = RequestMethod.GET)
 
public String createForm() {

   
return FORM_VIEW;
 
}

 
/**
   * This creates a new User object for the empty form and stuffs it into
   * the model
   */
 
@ModelAttribute("User")
 
public User populateUser() {
   
User user = new User();
    user.setFirstName
("your first name");
    user.setLastName
("your last name");
   
return user;
 
}

 
/**
   * This is the handler method. Stick the user bean into a new attribute for
   * display on the next page
   *
   *
@param user
   *            The user bean taken straight from the model
   *
@param model
   *            An out param. Takes the user and adds it to the model FOR the
   *            NEXT page under a different name.
   *
   */
 
@RequestMapping(value = PATH, method = RequestMethod.POST)
 
public String addUser(@ModelAttribute("user") User user,
      BindingResult result, Model model
) {

   
model.addAttribute("newUser", user);
   
return WELCOME_VIEW;
 
}
}

9 comments:

Anonymous said...

Good example, but you are using addAddress in your code instead of addUser.

Roger Hughes said...

Ooops. Now corrected.

Seungho Seo said...

I'm confused which method is second one that you mentioned "the second request handler method call doesn’t need the new user...".
If you say createForm(), I think createForm need new user for every request.

Roger Hughes said...

To clarify this point...

the addUser(...) method is the second request handler method annotated by @RequestMapping.

If a call is made to any method in the AddUserController class with the @RequestMapping annotation, then the populateUser() method is called first. In the case of a call to addUser(...), you don't need the User object returned by the populateUser(...) method. Hence, in a real world scenario, you just may suffer an unnecessary performance degradation, especially if populateUser() has to read default values from a database.

Hope that helps.

Seungho Seo said...

Thx for the great comment. It's very helpful.

TKirahvi said...

Is it possible to have a @ModelAttribute with not-required id field and populate it when accessing a @ResourceMapping method via ajax request?

I have a list of items on my page and I would like to fetch the data asynchronously when clicked on an item. The ajax call is catched with @ResourceMapping method with that item's id. But I don't seem to get the data on form however I try.

Ishan said...

In the addUser Mehod the ModelAttribute should be "User" rather than "user"

David said...

Locks great.
I wanted to do something like this as a starting project. Since I'm a beginner with Spring MVC it would be a great help to have the example as a complete example project. Specially with all the classes to see how they interact.
Can you provide something like this?

Roger Hughes said...

David,
I don't think that the source for this project is available; however, if you search for captaindebug on GitHub, you'll find lots of sample projects for other blogs on Spring.