Chris Greenhalgh, 2007-01-29; last updated 2007-02-02
The URL http://.../forms/form_abstract_controller_form.html
maps direct to the JSP form_abstract_controller_form.jsp,
which is a minimal HTML form, with a single text input called 'arg'.
The form action is to POST (by default, URL-encoded) to http://.../forms/form_abstract_controller.html.
This is mapped to an instance of the controller class equip2.webapptutorial.forms.FormAbstractController,
which is configured to return view named 'forms/form_abstract_controller_ok',
with the argument and answer passed via the model as 'arg' and 'answer'.
The FormAbstractController obtains the form value(s) directly from
the J2EE HttpServletRequest:
String arg = request.getParameter("arg");
This view is mapped to the JSP form_abstract_controller_ok.jsp
which prints a simple confirmation of the values.
The abstract Spring Controller class org.springframework.web.servlet.mvc.AbstractCommandController
is a suitable starting point for non-form commands (although we'll use
it with a simple form, here, for demonstration purposes).
The URL http://.../forms/abstract_command_controller_form.html
maps direct to the JSP abstract_command_controller_form.jsp,
which is an identical minimal HTML form, with a single text input
called 'arg'.
The form action is to POST (by default, URL-encoded) to http://.../forms/abstract_command_controller.html.
This is mapped to an instance of the controller class equip2.webapptutorial.forms.MyAbstractCommandController,
which is also configured to return view named 'forms/abstract_command_controller_ok',
with the answer passed via the model as 'answer'.
The controller method getCommand
is over-ridden to create an instance of the class equip2.webapptutorial.forms.FormCommandBean
(a simple Java bean with the String-typed property 'arg'), as required by the handle() method:
The abstract command controller's own implementation of handleRequestInternal calls getCommand to create an instance of the command object, and fill in its property values from the request parameters. This object is then passed to the handle() method as the command reference:
protected ModelAndView handle(HttpServletRequest request,The request parameter(s) can now be accessed from command object in a type-safe manner, and the Spring framework also supports some standard type coercions and a validation (checking) framework. E.g.:
HttpServletResponse response,
Object command,
BindException errors)
throws Exception
FormCommandBean formCommand = (FormCommandBean)command;
String arg = formCommand.getArg();
This command object may also passed to the view as the model
(request) attribute 'command'
(by default; the name can be changed by configuration).
The abstract Spring Controller class org.springframework.web.servlet.mvc.SimpleFormController is the preferred starting point for form handling.
For example, the URL http://.../forms/simple_form_controller.html
maps to an instance of the controller class equip2.webapptutorial.forms.MySimpleFormController.
By default, a simple form controller only considers HTTP POSTs to be
possible form submissions; a HTTP get simple returns the configured
form view, as specified by property 'formView' (in this case 'forms/simple_form_controller_form',
rendered by simple_form_controller_form.jsp).
As with the abstract command controller, above, the form is backed by a Java Bean, by default identified by the model name 'command'. In the simple form controller this is created by the method formBackingObject rather than getCommand. This form controller uses the same back class as above, equip2.webapptutorial.forms.FormCommandBean (a simple Java bean with the String-typed property 'arg'),
The form JSP (simple_form_controller_form.jsp)
has a submission action with the same URL (http://.../forms/simple_form_controller.html)
but method 'post'. The form also makes use of the Spring Taglib to help
with form error reporting, e.g. for the 'arg' input:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
...
<spring:bind path="command.arg">
'arg': <input
type="text"
value="<c:out value="${status.value}"/>"
name="<c:out value="${status.expression}"/>">
<c:if test="${status.error}">
Error:
<c:forEach items="${status.errorMessages}" var="error">
<c:out value="${error}"/>
</c:forEach>
</c:if>
</spring:bind>
Here, 'command.arg'
refers to the property 'arg'
of the form backing object (default name 'command').
When the form is first requested (HTTP get) the values are empty.
However, if a form is submitted but fails validation, then the old
(failed) value will be retained and the error reported as well. For
example, the following code snippet from onSubmit() shows an additional
check being made on the argument length:
FormCommandBean formCommand = (FormCommandBean)command;
String arg = formCommand.getArg();
// do further validation...
if (arg==null || arg.length()<3) {
// signal an error
errors.rejectValue("arg", "too-short", "Arg is too short (<3)");
return showForm(request, response, errors);
}
If the form can create a custom view as its result then the handling
of a (so far validated) submission is done by onSubmit (e.g.):
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors)
throws Exception
Otherwise doSubmitAction can be over-riden if no additional
information has to be passed to the model or view (the success view
will be returned with the command and any errors in the model):
protected void doSubmitAction(Object command)
throws Exception
In some cases, successful form submission may best be followed by a
redirect of the browser to a new URL; this can be done via the HttpServletResponse method sendRedirect(java.lang.String location),
via a Spring named view with a name of the form 'redirect:...'. or via a JSP:redirect from the success
view.
In general it is considered to be a *Bad thing* to compile into the
Java files text that will be returned directly to the user, since this
may need to be changed when the web site is tidied up, and would need
to be different in different languages. In the simple form controller
example, above, if the controller rejects the form then the details of
the error come from the controller and are presented to the user. The
recommended way of handling this - supported by the use of the BindException class ('errors' in the example) - is to
use a Spring Message Resolver to map message identifiers (such as 'too-short', above) into text
for display.
Message Resolver(s) must be specified for each Servlet, e.g. in forms-servlet.xml as
used here, there is the entry:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename"><value>forms</value></property>
</bean>
This causes the Spring 'forms' servlet to try to resolve messages
using a property bundle with base name 'forms', i.e. from the file WEB-INF/classes/forms.properties,
which in this case contains:
too-short.command.arg=The argument was too short (must be at least 3 characters long)
The dot-separated key is made up of the message (error) name ('too-short' - from the call to rejectValue), optionally the
object name (in this case 'command'),
and optionally in the case of field-specific errors the property name
(in this case 'arg', also
from the call to rejectValue).
If a message is not found for the error then the default message (if
any) is used (in this case, 'Arg
is too short (<3)' from the call to rejectValue).
File upload is easist in Spring using Command Controllers rather
than a simple abstract controller, i.e. using a backing object or
command object. Some additional configuration in the controller is
required to set up the support for reading a submitted form into a
backing object property of type byte[].
The URL http://.../forms/file_upload_form_controller.html
gives a form with a file-type
input element. Note that
the form method MUST be post, and the enctype must be "multipart/form-data".
File upload support is enabled by the controller overriding of initBinder:
/** initialise file upload support */
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
throws ServletException
{
binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
}
File upload support also requires that a multipart resolver is
configured in the corresponding forms-servlet.xml
configuration:
<!-- enable multipart handling using commons-fileupload.jar -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
The contents of the file is then directly available in the
controller. In this case the Controller rejects unspecified or
zero-length files.