Spring MVC: please explain difference between @RequestParam and @ModelAttribute
Asked Answered
S

5

68

I'm new to Spring MVC. Please help me unpack the documentation.

Documentation

Spring MVC Documentation states (emphasis mine):

  • @ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument’s fields should be populated from all request parameters that have matching names. The WebDataBinder class matches request parameter names — including query string parameters and form fields — to model attribute fields by name.

  • @RequestParam binds request parameters to a method parameter in your controller.

Disclaimer / Clarifier

I know that @ModelAttribute and @RequestParam are not the same thing, are not mutually exclusive, do not perform the same role, and can be used simultaneously, as in this question - indeed, @RequestParam can be used to populate fields of @ModelAttribute. My question is more geared towards the difference between their internal workings.

Question:

What is the difference between @ModelAttribute (used on a method argument, not method) and @RequestParam? Specifically:

  • Source: Do @RequestParam and @ModelAttribute have the same source of information / population, i.e. request parameters in URL, which may have been supplied as elements of a form / model that was POSTed?
  • Usage: Is it correct that variables retrieved with @RequestParam are thrown away (unless passed into a model), whereas variables retrieved with @ModelAttribute are automatically fed into the model to be returned?

Or in very basic coding examples, what is the real working difference between these two examples?

Example 1: @RequestParam:

// foo and bar are thrown away, and are just used (e.g.) to control flow?
@RequestMapping(method = RequestMethod.POST)
public String testFooBar(@RequestParam("foo") String foo,
@RequestParam("bar") String bar, ModelMap model) {
    try {
     doStuff(foo, bar);
    }
    // other code
  }

Example 2: @ModelAttribute:

// FOOBAR CLASS
// Fields could of course be explicitly populated from parameters by @RequestParam
public class FooBar{
    private String foo;
    private String bar;
   // plus set() and get() methods
}

// CONTROLLER
// Foo and Bar become part of the model to be returned for the next view?
@RequestMapping(method = RequestMethod.POST)
public String setupForm(@ModelAttribute("fooBar") FooBar foobar) {
   String foo = fooBar.getFoo();
   String bar = fooBar.getBar();
   try {
      doStuff(foo, bar);
   }
   // other code
}

My current understanding:

@ModelAttribute and @RequestParam both interrogate the request parameters for information, but they use this information differently:

  • @RequestParam just populates stand-alone variables (which may of course be fields in a @ModelAttribute class). These variables will be thrown away when the Controller is done, unless they have been fed into the model.

  • @ModelAttribute populates the fields of a class, which then populates an attribute of the model to be passed back to the view

Is this correct?

Stolon answered 31/3, 2015 at 14:7 Comment(0)
S
57

@RequestParam just populates stand-alone variables (which may of course be fields in a @ModelAttribute class). These variables will be thrown away when the Controller is done, unless they have been fed into the model.

Don't confuse the word "model" with session. The http conversation is generally: HTTP.GET, server response, then HTTP.POST. When you have the @ModelAttribute annotation in use you are always constructing an instance of whatever you have annotated, this is what makes you think that 'feeding things to the model' might make variables stick around. This isn't correct, once the HttpServletRequest has finished those variables should no longer be a part of the browser/server conversation unless they've been saved in a session.

@ModelAttribute populates the fields of a class, which then populates an attribute of the model to be passed back to the view

Yes! To be correct, @ModelAttribute tells Spring to use its default web data binder to populate an instance of something with data from the HttpServletRequest. Choosing to pass this data back to the view is up to the programmer. When you have a method annotated with @ModelAttribute, it is being called every time code hits that servlet. When you have @ModelAttribute as one of the method's parameters, we are talking about incoming Http form data-binding.

Calling @RequestParam is a shortcut for saying request.getParameter("foo"); under the hood, Java's HttpServletRequest lets you get values from the request object by doing a key->value look up. The value returned is of type Object. This is what you would be typing a lot if you were not using Spring in your web application.

Spring then takes this abstraction a step further when you start to use @ModelAttribute. This annotation employs the concept of data-binding. The goal of data-binding is that the code in your controller won't have to call request.getParameter("foo1"), for every form element. Imagine you have a web form with 5 fields. Without data-binding the programmer has to manually retrieve, and validate each of those fields. The programmer has to make sure that the request contains the property, that the property's value exists, and that the property's value is of the type expected for each field. Using @ModelAttribute tells Spring to do this work for you.

If you annotate a method in your controller with @ModelAttribute("fooBar") FooBar fooBar An instance of FooBar will always be constructed by Spring, and supplied to your method. Where the data binding comes into play, is when this annotation is used in a Method's parameters; Spring looks at the instance of HttpServletRequest and sees if it can match the data in the request to the right property on an instance of FooBar. This is based off the java properties convention, where you have a field such as foo and public getters and setters called getFoo and setFoo. This might seem magic but if you were to break convention, your Spring data binding would stop working because it wouldn't be able to know where to bind the data from your HttpServletRequest You would still get an instance of FooBar, but the properties would not be set to any values from the request.

Storybook answered 31/3, 2015 at 14:42 Comment(0)
G
27

@ModelAttribute: binds an entire Java object (like Employee). supports multiple request parameters

@RequestParam: binds a single request parameter (like firstName)

In general,
@RequestParam is best for reading a small number of params.

@ModelAttribute is used when you have a form with a large number of fields.

and

@ModelAttribute gives you additional features such as data binding, validation and form prepopulation.

Grosvenor answered 3/11, 2017 at 9:1 Comment(0)
E
5

@ModelAttribute annotated parameters are handled by a registered ServletModelAttributeMethodProcessor (or ModelAttributeMethodProcessor) and @RequestParam annotated parameters are handled by a registered RequestParamMethodArgumentResolver or RequestParamMapMethodArgumentResolver depending on the parameter type.

Here's an explanation of how Spring uses these HandlerMethodArgumentResolvers to resolve arguments for your handler methods:

In both cases, @ModelAttribute and @RequestParam, the values to be bound are retrieved from ServletRequest parameters.

You can look at the source code of the types mentioned above, but here are the simple details.

For @ModelAttribute, Spring will create an instance of the parameter type. It will inspect the fields of that instance and attempt to bind parameter values to them based on a naming/aliasing strategy composed of the @ModelAttribute name and the field names. It typically uses a group of Converter instances to convert from String (parameter values are always String values) to whatever the target field type Integer, Date, etc. You can also register your own Converter types for custom conversions. You can also nest POJO types.

For @RequestParam, Spring will use the same Converter instances to convert from the parameter values directly to the annotated parameter type.

Note that parameter values are not "thrown away". They are stored in the HttpServletRequest for the duration of the container's request processing cycle. You can always access them through the appropriate methods.

Eidson answered 31/3, 2015 at 16:5 Comment(0)
T
0

@ModelAttribute (parameter) loads a model attribute from @SessionAttributes or from @ModelAttribute (method).

You don't need it just to bind values from a request, but it will do that after loading from @SessionAttributes.

@RequestParam binds a request parameter to an object.

Trinity answered 31/3, 2015 at 18:27 Comment(0)
L
-1
  • At the Method Level

When the annotation is used at the method level it indicates the purpose of that method is to add one or more model attributes. Such methods support the same argument types as @RequestMapping methods but that cannot be mapped directly to requests.

@ModelAttribute
public void addAttributes(Model model) {
    model.addAttribute("msg", "Welcome to the Netherlands!");
}

A method that adds an attribute named msg to all models defined in the controller class.

Spring-MVC will always make a call first to that method, before it calls any request handler methods. That is, @ModelAttribute methods are invoked before the controller methods annotated with @RequestMapping are invoked. The logic behind the sequence is that, the model object has to be created before any processing starts inside the controller methods.

It is also important that you annotate the respective class as @ControllerAdvice. Thus, you can add values in Model which will be identified as global. This actually means that for every request a default value exists, for every method in the response part.

  • As a Method Argument

When used as a method argument, it indicates the argument should be retrieved from the model. if not present, it should be first instantiated and then added to the model and once present in the model, the arguments fields should be populated from all request parameters that have matching names.

In the code snippet that follows the user model attribute is populated with data from a form submitted to the addUser endpoint. Spring MVC does this behind the scenes before invoking the submit method:

**@RequestMapping**(value = "/addUser", method = RequestMethod.POST)
public String submit(@ModelAttribute("user") User user) {
    return "userView";
}

So, it binds the form data with a bean. The controller annotated with @RequestMapping can have custom class argument(s) annotated with @ModelAttribute.

This is what is commonly known as data binding in Spring-MVC, a common mechanism that saves you from having to parse each form field individually.

Linis answered 10/3, 2017 at 18:11 Comment(1)
This is literally copy-pasted from Baeldung's Spring MVC Tutorial.Reseat

© 2022 - 2024 — McMap. All rights reserved.