Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Form submit in Spring MVC 3 - explanation

I'm having problems understanding how does a form submit in Spring 3 MVC work.

What I want to do, is to create a controller which would take the user's name and display it to him. And somehow I have done it but I don't really understand how it works. So..

I have a form which looks like this:

<form:form method="post" modelAttribute="person">     <form:label path="firstName">First name</form:label>     <form:input path="firstName" />     <br />      <form:label path="lastName">Last name</form:label>     <form:input path="lastName" />     <br />      <input type="submit" value="Submit" /> </form:form> 

I also have a controller which looks like this:

@Controller public class HomeController {      @RequestMapping(value = "/", method = RequestMethod.GET)     public String showHelloPage(Model model) {         model.addAttribute("person", new Person());         return "home";     }      @RequestMapping(value = "/", method = RequestMethod.POST)     public String sayHello(Person person, Model model) {         model.addAttribute("person", person);         return "home";     } } 

To display a welcome message to a user I use the following code in the JSP page:

<c:if test="${not empty person.firstName and not empty person.lastName}">     Hello ${person.firstName} ${person.lastName}! </c:if> 

And it works (I omit the XML configuration files because they are irrelevant to the problem).

I thought that the "modelAttribute" attribute in a form points to the bean variable which should be populated with inputs' values (as set in their "path" attributes). But looks, it works in a very different way. If I remove the line

model.addAttribute("person", new Person()); 

from "showHelloPage" method I get an (common) exception "Neither BindingResult nor...".

Also, on the beginning, the "sayHello" method looked like:

(...) public String sayHello(@ModelAttribute("person") Person person, Model model) { (...) 

I mean, it had the "ModelAttribute" annotation. I added it, because in the tutorials I have read, it was always present. But after I removed it, everything worked well, as it did before.

So my question is - what is the use of the "ModelAttribute" anonnatation? Is it some way to omit a "modelAttribute" attribute in a form? And the second part, what is the way (maybe some annotation) to make a form automatically bind inputs' values to the proper bean's properties (which would be declared as a method parameter)? Without a need of adding an empty bean before sending a form (as I have to do it now).

Thanks for your replies (which aren't links to the Spring's documentation, because I have already read it).

like image 288
Michał Tabor Avatar asked Sep 22 '13 13:09

Michał Tabor


People also ask

What is Spring MVC form?

Spring MVC is a Model-View-Controller framework, it enables the separation of modules into Model, View, and Controller and uniformly handles the application integration. In this article, we will create a student login form and see how Spring MVC handles form-based web-based applications.

What is form backing object in Spring MVC?

Form Backing Object/Command Object This is a POJO that is used to collect all information on a form. It contains data only. It is also called a Command Object in some Spring tutorials. For example, an add a new car form page will have a Car form backing object with attribute data such as Year, Make and Model.

What is the use of Spring form?

Spring tag Library The 'form' tag renders an HTML 'form' tag. It exposes a binding path to inner tags for binding the data entered. It puts the command object in the PageContext so that the command object can be accessed by all the inner tags.

What is Spring form commandName?

The commandName attribute is the most important attribute in the form tag, which specifies the model attribute name that contains a backing object and the properties of this object will be used to populate the generated form.


1 Answers

The @ModelAttribute annotation in this case is used to identify an object that Spring should add as a model attribute. Model attributes are an abstraction from the HttpServletRequest attributes. Basically, they are objects identified by some key that will find their way into the HttpServletRequest attributes. You can do this by manually adding an attribute with Model#addAttribute(String, Object), have a @ModelAttribute annotated method, or by annotating a method parameter with @ModelAttribute.

The thing you need to understand is how Spring resolves your handler method parameters and injects arguments. It uses the HandlerMethodArgumentResolver interface to do so. There are a number of implementing classes (see javadoc) and each has the responsibility to resolveArgument() by returning the argument that Spring will use to invoke() your handler method through reflection. Spring will only call the resolveArgument() method if the HandlerMethodArgumentResolver supportsParameter() method returns true for the specific parameter.

The HandlerMethodArgumentResolver implementation in question here is ServletModelAttributeMethodProcessor which extends from ModelAttributeMethodProcessor which states

Resolves method arguments annotated with @ModelAttribute and handles return values from methods annotated with @ModelAttribute.

Spring (3.2) will register this HandlerMethodArgumentResolver and others

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {         List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();      // Annotation-based argument resolution     resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));     resolvers.add(new RequestParamMapMethodArgumentResolver());     resolvers.add(new PathVariableMethodArgumentResolver());     resolvers.add(new ServletModelAttributeMethodProcessor(false));     resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));     resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));     resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));     resolvers.add(new RequestHeaderMapMethodArgumentResolver());     resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));     resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));      // Type-based argument resolution     resolvers.add(new ServletRequestMethodArgumentResolver());     resolvers.add(new ServletResponseMethodArgumentResolver());     resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));     resolvers.add(new RedirectAttributesMethodArgumentResolver());     resolvers.add(new ModelMethodProcessor());     resolvers.add(new MapMethodProcessor());     resolvers.add(new ErrorsMethodArgumentResolver());     resolvers.add(new SessionStatusMethodArgumentResolver());     resolvers.add(new UriComponentsBuilderMethodArgumentResolver());      // Custom arguments     if (getCustomArgumentResolvers() != null) {         resolvers.addAll(getCustomArgumentResolvers());     }      // Catch-all     resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));     resolvers.add(new ServletModelAttributeMethodProcessor(true));      return resolvers; } 

When Spring needs to invoke your handler method, it'll iterate through the parameter types and through the above list and use the first one that supportsParameter().

Notice that two instances of ServletModelAttributeMethodProcessor are added (one after a //catch all comment). The ModelAttributeMethodProcessor has a annotationNotRequired field which tells it if it should look for the @ModelAttribute or not. The first instance must look for @ModelAttribute, the second one doesn't. Spring does this so that you can register your own HandlerMethodArgumentResolver instances, see the // Custom arguments comment.


Specifically

@RequestMapping(value = "/", method = RequestMethod.POST) public String sayHello(Person person, Model model) {     model.addAttribute("person", person);     return "home"; } 

In this case, it doesn't matter if your Person parameter is annotated or not. A ModelAttributeMethodProcessor will resolve it and bind form fields, ie. request parameters, to the fields of the instance. You shouldn't even need to add it to the model as the ModelAttributeMethodProcessor class will handle that.

In your showHelloPage() method

model.addAttribute("person", new Person()); 

is needed with the <form> taglib. That's how it resolves its input fields.


So my question is - what is the use of the "ModelAttribute" anonnatation?

To automatically add the specified parameter (or method return value) to the model.

Is it some way to omit a "modelAttribute" attribute in a form?

No, the form binding looks for an object in the Model and binds its fields to html input elements.

And the second part, what is the way (maybe some annotation) to make a form automatically bind inputs' values to the proper bean's properties (which would be declared as a method parameter)? Without a need of adding an empty bean before sending a form (as I have to do it now).

A Spring <form> tag latches onto a model attribute object and uses its fields to create input and label elements. It doesn't matter how the object ended up in the model as long as it did. If it can't find a model attribute with the name (key) you specified, it throws exceptions, as you saw.

 <form:form method="post" modelAttribute="person"> 

The alternative to providing an empty bean is to create the html yourself. All Spring's <form> does is use the bean's field names to create an input element. So this

<form:form method="post" modelAttribute="person">     <form:label path="firstName">First name</form:label>     <form:input path="firstName" /> 

Creates something like

<form method="post" action="[some action url]">     <label for="firstName">First name<label>     <input type="text" name="firstName" value="[whatever value firstName field had]" />     ... 

Spring binds request parameters to instance fields using the name attribute.

like image 62
Sotirios Delimanolis Avatar answered Oct 02 '22 16:10

Sotirios Delimanolis