Sunday, 3 January 2016

Using @ModelAttribute annotation in Spring MVC

Background

There are many cases in which you may need to populate your Model with some default data or bind the data in request in your model in controller/handler method. It are these cases when @ModelAttribute annotation comes in handy. So in this post we will see how exactly @ModelAttribute method works with some example.



Working of @ModelAttribute

  • Before starting I would like to assert your class should be a controller class for this to work which means your class should be annotated with @Controller annotation .
Next this  @ModelAttribute can be at
  1. Method level OR
  2. Method argument level.

We will come to each in a moment.  @ModelAttribute take an optional "value" that represents the name of this attribute. For eg. @ModelAttribute("test") Why we say it is optional? Because as per Springs "Convention over Configuration" principle it allows you to not to provide explicit value and in which case default name is assigned to the ModelAttribute. For eg. if you have a class called "Car" then the model attribute will be stored in the Model by name "car" (notice the small letter c).

The way this annotation works is as follows -

  1. Before invoking the handler method, Spring invokes all the methods that have @ModelAttribute annotation. So yeah a Controller annotated class can have multiple @ModelAttribute annotated methods. It adds the data returned by these methods to a temporary Map object. The data from this Map would be added to the final Model after the execution of the handler method.
  2. Then it prepares to invoke the the handler method. To invoke this method, it has to resolve the arguments. If the method has a parameter with @ModelAttribute, then it would search in the temporary Map object with the value of @ModelAttribute. If it finds, then the value from the Map is used for the handler method parameter.
  3. If it doesn't find it in the Map, then it checks if there is a SessionAttributes annotation applied on the controller with the given value. If the annotation is present, then the object is retrieved from the session and used for the handler method parameter. If the session doesn't contain the object despite of the @SessionAttributes, then an error is raised.
  4. If the object is not resolved through Map or @SessionAttribute, then it creates an instance of the parameter-type and passes it as the handler method parameter. Therefore, for it to create the instance, the parameter type should be a concrete-class-type (interfaces or abstract class types would again raise an error).
  5. Once the handler is executed, the parameters marked with @ModelAttributes are added to the Model.

Above is the general working of the annotation at method level or method argument level.


Styles for using ModelAttribute

You can use one of the following styles for adding ModelAttributes to Model - 

  1. Implicitly return variables which will get added to the Model


    @ModelAttribute("data")
    public String addData() {
        return "test";
    }
    

    NOTE : Above "test" String will be added to the Model as name "string"(small s) i.e string=test. If you do something like below -

    @ModelAttribute
        public List<String> addTest() {
            return Arrays.asList("newValue");
        }
    


    then name will be "stringList" i.e stringList=[newValue].
  2. OR get the Model object and explicitly add multiple data to it

    @ModelAttribute
    public void addData(Model model) {
        model.addAttribute("test");
        model.addAttribute("tes1");
        // add more ...
    }
    



    NOTE : If you add in model two attributes with same name the later one will override the 1st one. Consequently in above code "test1" will override "test" as value for name "string" which is constructed with convention i.e string=test1(After all it is a Map underneath and keys of Map are unique (if you remember keys returns set data structure)).

Examples

Now let us see examples that will help us understand above concepts even more.

Following is a simple Controller class - 

@Controller
public class WelcomeController {

    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome() {
        return "welcome";
    }
    
    @ModelAttribute("test")
    public String addTest() {
        return "testValue";
    }
    
}

Here is what is happening -  As we know before handler methods are executed all methods annotated with @ModelAttribute are execute. Consequently out addTest() method gets executed and adds a String with name "test" in the Model. You can access the same in your JSP called "welcome.jsp" as follows - 

  • <h1><c:out value='${test}' /></h1>
and here you will see  "testValue" is h1 heading size.

You can also access this ModelAttribute in your handler method as follows -

    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome(@ModelAttribute("test") String test) {
        System.out.println(test);
        return "welcome";
    }


and you should see "testValue" getting printed in your server console. You can even override the value of "test" ModelAttribute in your handler class.

    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome(@ModelAttribute("test") String test, Model model) {
        System.out.println(test);
        model.addAttribute("test", "newTestValue");
        return "welcome";
    }


and now your JSP should show "newTestValue" in h1 heading.

NOTE: When using controller interfaces (e.g. for AOP proxying), make sure to consistently put all your mapping annotations — such as @RequestMapping and @SessionAttributes — on the controller interface rather than on the implementation class.

NOTE : SessionStatus.setComplete() method will trigger cleaning of Session Attributes, but only those which Spring will find "actual session attribute". Suppose that you declare 3 session attributes, but use only 1 of them in your handler method parameters then only that will be cleared.

Related Links

t> UA-39527780-1 back to top