Form submit in Spring MVC 3 - explanation
Solution 1:
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.