Table of Contents
References for Spring include: [SWS], [HM05], [JH04], [Johnson03], [JHA05], [Raible04] and [WB05].
A project[AppFuse] has been set up to demonstrate how to integrate a number of different technologies, including Spring and Hibernate, into a realistic but simple web application. This is well worth downloading and studying.
Three main concepts underly the Spring framework:
Inversion of Control (IoC), also known
as Dependency Injection. Implemented
via the BeanFactory class hierarchy.
Application Context (AC), implemented
via the (rather obviously named)
ApplicationContext class, which extends
BeanFactory.
Aspect Oriented Programming (AOP). Implemented by means of various kinds of proxies, reflection and dynamic, runtime byte code generation.
To get you started, I have written a couple of sample applications:
SpringSimple.jar:
This contains the sources of a console Spring application that
demonstrates almost the simplest possible example of
IoC, AOP with an
ApplicationContext.
SpringHibernate.jar: This contains the sources of a console Spring application that uses Hibernate and transactions on the book shop database that we have been working on.
You should download the above applications, get them working and then experiment by trying to add extra use cases (of your own choice) to the SpringHibernate application.
A BeanFactory is a
Spring class that creates, configures and manages Java
objects. The configuration details are typically kept in xml or
property files.
An ApplicationContext is a
Spring class that extends
BeanFactory, and therefore provides all the
same functionality, but which also provides extra enterprise level
functionality such as internationalisation support, resource
access (i.e. files and URLs), Event propagation (i.e. via event
listeners) and hierarchical contexts.
The java objects created by a BeanFactory or
ApplicationContext are, by default, singletons,
i.e. only one such object will be created and used in the whole
application, and each request for an object of that type will
return the same object — this is, in fact what we usually want for,
for example, data sources, data access objects etc. A true
BeanFactory will create these objects on
demand, whereas an ApplicationContext will create them when it
itself is created.
Non-singleton beans (called prototypes can
also be created. This can be seen as a more powerful version of
the Java new command, because as well as
creating the object, the BeanFactory can initialise it and set the
objects properties to refer to other beans in the system (this is
called wiring it up). However, while the
BeanFactory will manage the entire lifecycle of
a singleton object, it will not manage that of a
prototype bean. Both true
BeanFactory and
ApplicationContext classes create
prototypes only on demand.
The bean configuration is usually specified in an xml file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="helloWorld" class="helloworld.HelloWorld">
<property name="message">
<value>Hello World</value>
</property>
</bean>
</beans>
The configuration file contains a list of bean
elements, each describing a single object configuration. Actually,
it can also contain import elements, which
reference other bean configuration files that should be included
and a description element for documentation
purposes. The most common use of the most important elements and
attributes of the configuration file are described below. For the
rest, see [SRM].
A bean element will normally have a number of
attributes:
class: the fully qualified class name of
the object to be created. This is usually required
(exceptions occur only with more advanced configuration
specification).
id or name: this is
the name by which the bean will be referred to both within
the configuration file and by code which requests the bean
from the BeanFactory or
ApplicationContext. You should normally
use the id attribute, as this has special
significance to XML files (as specified in the DTD file),
and the xml parser in Spring is then able to use it to do
extra correctness checking of the configuration file.
However, if you need to use characters for the name that are
not legal in XML id attributes, you can
use name instead.
singleton: this attribute is optional
and, by default is true. Set it to false if you want this
bean to be a prototype. Normally one
does not need to use prototypes.
init-method: optional no-argument method
to invoke after setting all the bean's properties for doing
some extra initialization beyond what is possible through
setting properties alone.
destroy-method: optional no-argument
method to invoke when the BeanFactory
shuts down. Note that this is only invoked for singleton
beans, the BeanFactory does not retain
any references to prototype beans.
Inside a bean element, we normally specify a
sequence of properties of the bean and the values that those
properties should be set to when the
BeanFactory creates the object.
[To be continued ...]
Spring provides support for the presentation layer of a web application too. Actually it provides support for many different types of presentation layers, but we will look specifically at its support for the Model-View-Controller pattern in web applications using Java Server Pages. Raw Java Server Pages are rather painful to write so we will allow ourselves a little anaesthetic in the form of the JSP Standard Tag Library (JSTL). Our first simple Spring MVC application can be downloaded from SprMVC.jar. You should download these files and set up a project in your IDE to get them working. Make sure that you can compile the .java files, build a web deployment directory structure, startup tomcat, deploy the application and debug it, all from within Tomcat. Getting this sorted out early on will make future development of web applications much easier.
WEB-INF/jsp/home.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html> <head><title>Greeting</title></head> <body> <h2><c:out value="${message}"/></h2>
</body> </html> }
src/HomeController.java:
import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HomeController implements Controller{ private String greeting; public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception { String s = greeting + ": the date and time is now " + Calendar.getInstance().getTime() ; return new ModelAndView("home", "message", s);
} public void setGreeting(String greeting) { this.greeting = greeting; } }
SprMVC/WEB-INF/controller-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="/welcome/home.html"
class="HomeController">
<property name="greeting">
<value>
Welcome to Spring MVC
</value>
</property>
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>
controller
</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>
1
</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>
controller
</servlet-name>
<url-pattern>
*.html
</url-pattern>
</servlet-mapping>
</web-app>
![]() |
We have to tell is to tell the servlet container (Tomcat,
in our case) who we are. This entry says that our
application has a servlet that we we refer to as
|
![]() |
Finally, we have to tell the servlet container what
patterns of web addresses should be sent to the controller
for processing. As we want all requests to go through the
servlet, and we don't particularly want to show the end
user what technology we are using, we use the pattern
|
Thus, in summary, and although there can be exceptions, we generally have to set up:
One DispatcherServlet to farm out
requests to our controllers
One controller bean for each type of request which processes
the request and returns a ModelAndView object
One view for each type of response
A mechanism for mapping controllers to web addresses: a
HandlerMapping class (if you do not
specify this you get
BeanNameUrlHandlerMapping by default)
A mechanism for resolving view names to views: a
ViewResolver class
For the remainder of this section, we will discuss a larger example that demonstrates different controller types, command and form input and validation. Download the files from SprMVCFull.jar
We have seen how, in Spring, the
Controller part of the
Model-View-Controller pattern is actually
divided into two parts: a DispatcherServlet
that actually fields the incoming requests and classes that
implement the Controller interface to process
the operation. The following classes and interfaces in the
Controller hierarchy are the most used (there
are others as well):
Table 1. Spring Controller classes
| Class | Purpose |
|---|---|
Controller
| Top level interface of the entire Controller hierarchy. One should normally not implement this controller directly but rather extend a more functional class that implements it. |
AbstractController
|
The simplest concrete class that implements
Controller. It is relatively unusual to
extend this class, but it is used if you want to
invoke an operation that takes no parameters at all.
|
AbstractCommandController
| Handles simple command style requests but does not provide much in the way of form support: use for simple commands or where command arguments are passed as parameters of the request URL rather than as fields in a form. Not appropriate where the use has to type in parameters but fine if the parameters are hard coded into the web page and chosen, say, by clicking on an appropriate button. |
SimpleFormController
| Handles forms submission: supports specification of the form view to display the form and the success view when the form submission succeeds. The form view is redisplayed if validation fails and the Spring tags support filling the form with the previously entered data and displaying field specific and global errors on the form. |
AbstractWizardFormController
|
Handles multi-page wizard forms: supports navigation
between the pages, per page and whole form validation
and all the features of
SimpleFormController as well.
|
MultiActionController
| Allows using a single controller to encode multiple similar controller actions (i.e. one method per controller instead of one class per controller). |
An AbstractController is a suitable class
to extend if you want a controller that does not access any
parameters from the request object (i.e.,
any parameter set on the web page). Of course, one
can still access request parameters by
querying the request object directly (it
is, after all, passed as a parameter to the
handleRequest method) but if one needs such
parameters, one should use one of the command or form
controllers instead. The advantage that
AbstractController provides over a simple
Controller is that it provides support for
synchonising on the session (see Section 3.3, “Preventing Duplicate Form Submission”) and it
extends WebContentGenerator, which provides
support for cache control of the web pages, requiring a
session to be present when the controller is invoked and
choosing whether a HTML POST or
GET or both are allowed to invoke this
controller: see the API documentation for details. This
features are normally configured via the
Spring application context file.
In our example program, sprMVCFull.controller.HomeController
extends AbstractController:
package sprMVCFull.controller;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
public class HomeController extends AbstractController
{
private String greeting;
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
String s = greeting +
": the date and time is now " +
Calendar.getInstance().getTime() ;
return new ModelAndView("home", "message", s);
}
public void setGreeting(String greeting)
{
this.greeting = greeting;
}
}
As we can see, this controller does not take any data from
the input web page (although it is adding some to the output
page) so an AbstractController is very
appropriate here.
The sprMVCFull.controller.HomeController
class is embedded in our web space at
/hello.html via setting a property in the
controllerMapping bean in the context file,
WEB-INF/controller-servlet.xml.
To invoke this controller, we need a link to
/hello.html from a web page. We could use a
normal html link, a button that uses a little
Javascript to go to the address or a
submit button in a form. Whatever we do, we should wrap the
URL in a c:url JSTL tag to ensure that url
rewriting is done correctly in case the user has disabled
cookies and we need to preserve the connection between the
user's request and a Session object. In
WEB-INF/jsp/index.jsp, the following line
does this correctly for a simple link:
<a href='<c:url value="/hello.html"/>'>
An AbstractCommandController is intended to
add the automatic handling of parameters from a web page
request to the functionality of
AbstractController. The idea is that one
specifies the model class (i.e., the class of the business
object) to copy the request parameters into. Within the
controller, one can then access those values in a simple
manner without having to process the
request object directly. The
sprMVCFull.controller.PrintParamController
class is an example:
package sprMVCFull.controller;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.validation.BindException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sprMVCFull.model.PrintParamCommand;
public class PrintParamController extends AbstractCommandController
{
public PrintParamController()
{
setCommandClass(PrintParamCommand.class);
}
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors) throws Exception
{
return new ModelAndView("printCommand", "command", ((PrintParamCommand)command).getParamName());
}
}
We have to specify the class that the parameters should be
copied into, in our case the
sprMVCFull.model.PrintParamCommand class,
and when the controller is invoked, the
AbstractCommandController super class
methods will ensure that all parameters in the request whose
names match properties in the model bean will be copied into
the model bean.
In fact, Spring MVC is much more
sophisticated that merely to support "copying into the model
bean". For any property in the model bean whose type is not
String, Spring will
try to use one of its built-in
PropertyEditors to transform the
String obtained from the request and set
the appropriate property in the model bean by doing a suitable
transformation of the string. Thus for a property of any of a
number of numeric types (int,
float, Integer, etc.),
Spring will invoke its
CustomNumberEditor to parse the string into
the appropriate numeric type. Similarly there are built-in
editors for boolean and for String arrays
(the latter transforms a comma-separated string into an array
of Strings). There are others as well,
including a date editor, although that is not registered by
default so must be registered explicitly if required. You can
also define your own custom editors and register them with the
request parameter binder in order to handle your own
classes if you wish.
The
sprMVCFull.controller.PrintParamController
class is mapped into our web space at /printCommand
so again we can use a link, a button with javascript or a
button in a form to invoke it. Again we need to wrap the URL
in a c:url JST tag to handle URL rewriting.
However, we also need to set the parameter, in our case, a
property of type String with name
"paramName":
A link approach; note that the parameter appears as part of the address.
<a href='<c:url value="/printCommand.html">
<c:param name='paramName'>
Link
</c:param>
</c:url>'>
PrintCommand(Link)
</a>
A button with javascript; again the parameter appears as part of the message but also this approach will fail if the user has turned off javascript in his or her browser.
<button onclick="location.href='<c:url value="/printCommand.html">
<c:param name='paramName'>
Button
</c:param>
</c:url>'">
printCommand(Button)
</button>
A form based approach; this is the preferred way to
use a button as it does not require javascript to be
enabled. Also this does not display the parameter as part
of the address as the form method chosen was
"post". If you want the parameter to be
part of the address (so that, for example, the user can
bookmark the resulting page), simply change the method to "get".
<form action='<c:url value="/printCommand.html"/>' method="post"> <button name="paramName" value="FormButton" type="submit">printCommand(FormButton)</button> </form>
If the user explicitly enters input data on a web page, instead of simply clicking on one of the available links, then there is a possibility that the data entered will be invalid for some reason: perhaps he or she has mis-spelled a word or entered a value, or a combination of values, that is not allowed. In this case we want to be able to easily check the data entered and, if there is a problem with it, to re-display the same page back to the user, with the incorrect data still on it, so that the user can correct the data without having to retype everything all over again. Furthermore, we would like to be able to display appropriate error messages on the page to indicate to the user what the problem is.
The key to understanding
SimpleFormController is to realise that
there are three separate issues involved:
There is a model object associated with the controller, which has to be created (with the default constructor) or initialised (explicitly by the programmer) the first time into the controller but on second and further invocations of the controller during the processing of the form the values for the model object must come from the form.
There are two different ways the controller gets invoked, from pages that do not contain the form but want the form to be displayed, and from the form page itself when the form is submitted.
There are (usually) two different ways that processing can continue from the controller: if there was an error in the form data, then the form page should be resdisplayed with the incorrect data and appropriate error message, and if the data is correct then the use case should be processed and the next page should be displayed.
First, let us consider the model object associated
with the controller. The relevant controller in our example is
sprMVCFull.controllerProcessDetailsController.
The model object associated with this controller (and form) is
sprMVCFull.model.Person. A fairly minimal
implementation of the controller is as follows:
package sprMVCFull.controller;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
import org.springframework.validation.BindException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import sprMVCFull.model.Person;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ProcessDetailsController extends SimpleFormController
{
public ProcessDetailsController()
{
setCommandClass(Person.class);
}
protected ModelAndView onSubmit(Object command) throws Exception
{
Person person = (Person) command ;
// This debug call represents the real business use case
logger.debug("got person: " + person) ;
return new ModelAndView("printCommand", "command", command.toString());
}
}
In the constructor, we configure the class used as the model object for the form. The only reason we need to do this is so that the framework can create a new model object of the correct type when we first get the form submitted. Of course, we could avoid doing this configuration here and instead do it in the application context file where we wire up all our context beans. However, I have left this configuration here because sometimes we want to replace that configuration with something else: namely, we sometimes want to create not just a default object of the appropriate type, but one which is pre-initialised to some valuesFor our example, we might want the person details shown to be those of the user, so that he or she can then modify and update them. To this, we replace the four lines of the constructor above with the following:
protected Object formBackingObject(HttpServletRequest request)
throws Exception
{
Person p = new Person() ;
p.setFirstname("John");
p.setLastname("Smith");
p.setEmail("J.Smith@somewhere.co.uk");
p.setGender("M");
return p;
}
Of course, in practice the model object would, most likely, be retrieved from a database.
Now that our model object is taken care of, we can consider how the controller is invoked.
The simplest case is from the page containing the form. Here the form data is sent, as parameters in the request, when the user clicks on a submit button in the form. Automatic binding of the data from the form into the model object should occur, validation of the model object can then take place, redirecting back to the form page if there were errors, before handing off to the controller for dealing with the use case.
The second case is when another web page, which does not
contain the form, wishes to trigger the presentation of
the form. In this case, we want that the controller
creates a new model object, or obtains an initialised one
use the formBackingObject described
above. This object should then be used to set the
initial values of the form input fields and the form
page should be displayed.
The question remains, how does Spring
distinguish between the two cases? The default is that if the
incoming request used a POST method, then
Spring assumes that it is a submission of
data from the form. If, however, the request used a
GET method, then an initial request is
assumed and no attempt is made to extract the form data from
the request. In practice, this matches extremely well with the
standard use of requests and form page invocation and submission.
Finally, we look at how processing continues out of a form controller. There are usually two cases:
If the incoming request was a GET, or
if there was an error with the data from the form, we
wish to display the form view. Since we can construct,
or already have, the model object, the only extra piece
of information required to display the form view is the
name of the view itself, Spring
takes care of the forwarding to the view so long as we
tell it the view name. This is done by setting the
formView property of the controller,
and this, in turn can be done in the application context
file (which is where I did it for the example code).
If the incoming method was a POST,
then the request came from the form web page and the
model object has passed validation. Now we want to
execute the use case and proceed to the next, or
success page. We could do this in a
similar way to the formView, by
setting the successView property and
overriding the simpler protected void
doSubmitAction(Object command) method of the
controller instead of the onSubmit
method. the doSubmitAction is simpler
because it does not have access to the
request or
response objects nor does it have
to return a ModelAndView: instead the
framework ensures that the
successView, as set via property of
the same name, is presented when
doSubmitAction returns. I tend not to
use because usually I want the success view to also
display some model data as well, and the
doSubmitAction does not support that.
This may also be a point against setting the
formView in the application context
file, as it is a questionable practice to set the two
views in two different ways in two different files. A
solution is to set the formView in
the constructor explicitly.
Sometimes there is one other way to proceed from the controller: if you wish to allow the user to cancel the form altogether then you need to shortcut the standard procedure for dealing with the form. Otherwise, when the form data is submitted, validation will take place and if the data is not correct, the user will be returned to the input form until it is filled in correctly. It is not resonable to force a user to correctly fill in a form that he wishes to cancel.
The solution is intercept the request before it goes through model object validation, check if this is a cancel operation, and, if it is not, continue where you left off. If it is a cancel, however, you can redirect to a suitable alternative ModelAndView. This can be done by adding the following code to the controller:
private static final String PARAM_CANCEL = "_cancel" ;protected ModelAndView processFormSubmission(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors) throws Exception { if (WebUtils.hasSubmitParameter(request, PARAM_CANCEL))
return new ModelAndView("printCommand", "command", "form submission cancelled") ;
return super.processFormSubmission(request, response, command, errors);
}
![]() | First we need to set the name of the parameter that will be set when the user presses the cancel button |
![]() | Overriding this method is only necessary if you want to do some processing BEFORE the validation of the model for the form. |
![]() | Since we have intercepted the dispatching to the controller before handling of the model object has taken place, we must search for the parameter directly in the request object. Fortunately, Spring provides this utilities class that makes that search simple. |
![]() |
If the operation invoked was a
|
![]() |
If the operation invoked was not a
|
So far, we have discussed the code in the controller class, and we will not discuss the code in the model object as it is a standard java bean with no special or Spring specific aspects at all. However, we have not discussed how validation of the model object works. To do this we add a special validator class which is specific to the controller (and then wire it in via the application context file). The wiring is accomplished, in our case by adding the following lines to the application context file under the bean for our form controller:
<property name="validator">
<bean class="sprMVCFull.model.PersonValidator"/>
</property>
The class referred to must implement the
Validator interface to support validation
of the model object, and typically the model
object itself will do so, but one can, as in the example code,
have an independant class provides the validation support:
package sprMVCFull.model;
import org.springframework.validation.Validator;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import java.util.regex.Pattern;
public class PersonValidator implements Validator
{
public boolean supports(Class clazz)
{
return clazz.equals(Person.class);
}
public void validate(Object obj, Errors errors)
{
Person p = (Person) obj ;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstname",
"required.firstname", "First name is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastname",
"required.lastname", "Last name is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "gender",
"required.gender", "Gender is required");
validateEmail(p.getEmail(), errors);
}
private static final Pattern EMAIL_PATTERN = Pattern.compile("^\\w+([_\\.-]\\w+)*@(\\w+([_\\.-]\\w+)*)");
private void validateEmail(String email, Errors errors)
{
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email",
"required.email", "Email address is required");
if (!EMAIL_PATTERN.matcher(email).matches())
{
errors.rejectValue("email", "invalid.email", "Email address is invalid") ;
}
}
}
In the above code, the Validator requires
implementation of two methods: the support
method will be used by Spring to check
which of a possible collect of Validator
methods is suitable for validating objects of a particular
class. The second, validate, actually
carries out the validation. The first argument will be the
model object with its properties set from the corresponding
request object parameters but with no validation yet applied.
The second is an errors object that
essentially contains a collection of errors found so far. If,
on return from this method, the error collection is empty,
that validation will be considered to have succeeded and
processing can continue. Otherwise it will have failed and the
form view will be show again.
Error objects are created and added to the
errors collection object either by calling
the reject or the
rejectValue method on the
errors object itself, or by using the
convenience methods of ValidationUtils. In
general, the Value form of the methods
are for errors in a specific field or property of the
model object whereas the other methods are used to signify
global or multi-field problems.
All that is left now is to integrate the error messages and the model object property values into the forms for display to the user. This is done with a small number of Spring tags. Note that we do not need to use these tags in general: without them, we do not get the error messages on the form or the intial values for the input fields, but copying of form input values to the model object on submission of the form would still work.
<<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %><html> <head> <title>Books Rn't Us</title> </head> <body> <h1> Test of a SimpleForm Controller </h1> <p> <form action='<c:url value="/processDetails.html"/>' method="post"> <p> <spring:hasBindErrors name="command">
<c:forEach var="err" items="${errors.globalErrors}"> <span class="error"><c:out value="${err.defaultMessage}"/></span><br /> </c:forEach> </spring:hasBindErrors> First name: <spring:bind path="command.firstname">
<input type="text" name="firstname" value="<c:out value="${status.value}"/>"> <span class="error"><c:out value="${status.errorMessage}"/></span> </spring:bind> <br /> Last name: <spring:bind path="command.lastname"> <input type="text" name="lastname" value="<c:out value="${status.value}"/>"> <span class="error"><c:out value="${status.errorMessage}"/></span> </spring:bind> <br /> email: <spring:bind path="command.email"> <input type="text" name="email" value="<c:out value="${status.value}"/>"> <span class="error"><c:out value="${status.errorMessage}"/></span> </spring:bind> <br /> <spring:bind path="command.gender">
<input type="radio" name="gender" value="M" <c:if test='${status.value == "M"}'>checked</c:if> > Male <span class="error"><c:out value="${status.errorMessage}"/></span> <br /> <input type="radio" name="gender" value="F" <c:if test='${status.value == "F"}'>checked</c:if> > Female <br /> </spring:bind> <input type="submit" value="Send"> <INPUT type="Reset"> <input type="submit" value="Cancel" name="_cancel"> </p> </form> </body> </html>
![]() | Since we are using the Spring tags, we need to declare them to the JSP page. |
![]() |
Here we print out the global errors that are not
specific to a single property of the model object but
are caused by the state of a combination of different
properties. Note that each errors object is specific
to a single object so here we wrap the error printing
code in a spring tag that does nothing if the named
object has no errors. Otherwise it sets the
Within the tag then, we can iterate, using the JSTL tags, over the list of global errors in the errors object, printing out the default message of each. |
![]() |
For an actual input field, we surround it with a
With this tag, we can refer to objects under the name,
local to the
It seems a little inelegant that one has to specify
the property associated with the input field twice:
once for the path of the
<spring:bind path="command.firstname">
<input type="text" name="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</spring:bind>
Finally the |
![]() |
We are faced with a slightly different problem when
dealing with radio buttons and option groups. Here
each |
A particular situation with respect to forms that frequently
arises is where a sequence of forms have to be filled in and
each form in the sequence contributes only some properties of
the model object. In fact,
AbstractWizardFormController extends
AbstractFormController, which is almost all
of SimpleFormController so most of the
discussion of SimpleFormController also
applies here. Therefore I will concentrate on the differences.
These include
Managing the navigation between pages
Validating the input from a single page instead of the whole model object at once
The model object itself is, as usual, just an ordinary Java
bean. For the sake of the example, I assume that it has a
number of String properties to represent
various fields in a survey.
Unlike in the SimpleFormController case, we
do not have a single form view but one for each page of the
wizard. We set up the pages in the application context file,
WEB-INF/controller-servlet.xml by setting
the pages property of the controller bean
to a list of views:
<bean id="surveyController" class="sprMVCFull.controller.SurveyController">
<property name="pages">
<list>
<value>survey_colour_food</value>
<value>survey_country_language</value>
<value>survey_film_singer</value>
</list>
</property>
</bean>
The views in the list are just standard form views. I will show only the last one of the three:
<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title>Books Rn't Us</title>
</head>
<body>
<h1>
Test of a Wizard Controller
</h1>
<p>
<form action='<c:url value="/survey.html"/>' method="post">
<p>
<spring:hasBindErrors name="command">
<c:forEach var="err" items="${errors.globalErrors}">
<span class="error"><c:out value="${err.defaultMessage}"/></span><br />
</c:forEach>
</spring:hasBindErrors>
What is your favourite film:
<spring:bind path="command.film">
<input type="text" name="film" value="<c:out value="${status.value}"/>">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</spring:bind>
<br />
What is your favourite singer:
<spring:bind path="command.singer">
<input type="text" name="singer" value="<c:out value="${status.value}"/>">
<span class="error"><c:out value="${status.errorMessage}"/></span>
</spring:bind>
<br />
<input type="submit" value="Previous" name="_target1">
<input type="submit" value="Finish" name="_finish">
<input type="Reset">
<input type="submit" value="Cancel" name="_cancel">
</p>
</form>
</body>
</html>
![]() |
As with the |
![]() |
You can have as many wizard page navigation buttons as
you like. The name has to be
|
![]() |
The "finish" button is a normal submit button which
must have the name |
![]() | The reset button is handled directly by the browser without submitting the form to the server. Note that it does not clear the fields but resets them to the initial values they had when the page was first displayed. |
![]() |
The cancel button is handled in exactly the same way
as for the |
We have seen how the navigation between pages is specified on
each view, on the basis of the list of views set for the
controller bean in the application context file. The actual
processing of the navigation is done in the controller itself
of course. However, the code to do so is in the
AbstractWizardFormController superclass,
and no code need to be added to your controller to make it
work. In effect, navigation between pages is handled invisibly
to your code in the same way that, in
SimpleFormController, a submission of a
form which fails validation gets redirected back to the form
view which you having to write explicit code to make it so.
You do have to write code to hand the final submission
triggered by clicking the finish button
however, and there is still the validation to consider (in
sprMVCFull.controller.SurveyController):
package sprMVCFull.controller;
import org.springframework.web.servlet.mvc.AbstractWizardFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sprMVCFull.model.Survey;
public class SurveyController extends AbstractWizardFormController
{
public SurveyController()
{
setCommandClass(Survey.class);
}
protected ModelAndView processFinish(HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors)
throws Exception
{
Survey survey = (Survey) command ;
return new ModelAndView("printCommand", "command", survey) ;
}
protected ModelAndView processCancel(HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors)
throws Exception
{
return new ModelAndView("printCommand", "command",
"survey form submission cancelled") ;
}
protected void validatePage(Object command, Errors errors,
int page, boolean finish)
{
Survey survey = (Survey) command ;
switch (page)
{
case 0:
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "colour", "required.colour", "colour is required");
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "food", "required.food", "food is required");
break ;
case 1:
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "country", "required.country", "country is required");
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "language", "required.language", "language is required");
break ;
case 2:
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "film", "required.film", "film is required");
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "singer", "required.singer", "singer is required");
break ;
default:
}
if (finish)
{
if (!"ireland".equals(survey.getCountry().toLowerCase()))
errors.reject("invalid.country",
"bad choice of favourite country (try \"Ireland\")!") ;
}
}
}![]() | When the fully completed form is submitted (via the finish button), we handle it like any form submission. |
![]() |
In an |
![]() |
Validation is basically similar to
SimpleFormController validation, except that, during
the filling in of a wizard form, you only want to
validate the data submitted on a page by page basis
until the whole form is complete. This is done by
switching on the |
![]() |
If the |
In the (unmodified) SprMVCFull application, try the following:
Go to the ProcessDetails form, fill it in and
submit it. Then use the browser back button and submit the form
a second time. As you can see, the second submit also works. In
a real web application, this behaviour could lead to customers
unintentionally buying more than one holiday, flight, book or
other item. To prevent that we need to be able to detect that
the submission is a duplicate submission and we need to be able
to take action on that occurrence. In
Spring there is support for both these
requirements.
![]() | Note |
|---|---|
A well designed web front end should prevent duplicate form submission for all those forms where duplicate form submission would be bad. Thus duplicate form submission for confirming an order should be stopped so that a user does not accidentally, for example, book the same flight twice. On the other hand, you should probably not stop duplicate submission of a simple search form: if you have a form used to search a catalog, for example, it is convenient to be able to enter details, execute the search, hit the back button, modify the details and search again. This is duplicate form submission but causes no harm as no important state is being updated on each submission. Therefore, for every form, you should decide whether you need to prevent duplicate submission or not, and have a clear explanation (and documentation) for why. | |
For the detection part we simply need to store some information
across the form view display from the controller and the
following request from the user that contains the form data to
be submitted. We set up the information before returning the
ModelAndView from the controller. When the
resulting form is submitted by the user, we discard the
information. Thus if a valid (first) form submission arrives,
then the stored information will be there, can be discarded, and
everything is fine. However, if a form submission comes in for
which the stored information has already been discarded, then
this must be a duplicate submission. The information stored can
be anything so long as it cannot be confused with any other
form. Spring simply uses the model itself.
The only question remaining is where to store the information.
It cannot be stored in the request object
as that only lives from an initial request from the user, to the
controller and back to the user. However we need the information
to live from a response from the controller, to the user and
back to the controller: For the first half of that interchange,
we have one request object, and for the second one, a totally
different, unrelated one. Hence we save the information in the
session. This is done by adding the
following lines to the controller bean definition in the
controller-servlet.xml file:
<property name="sessionForm"><value>true</value> </property> <property name="synchronizeOnSession">
<value>true</value> </property>
![]() | Store the form data object in the session for the duration of the form entry |
![]() | Specify that two parallel submits associated with the same session must synchronize on the session. This stops race conditions in the unlikely situation where a user does a duplicate submission where the second submission, as far as the web application server is concerned, is actually in parallel with the first one. |
![]() | Note |
|---|---|
The above code is necessary to detect duplicate form
submission for controllers that directly extend
| |
In order to take action on a duplicate submission, we merely
have to override the handleInvalidSubmit
method of any form controller class. For example:
protected ModelAndView handleInvalidSubmit(HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
return new ModelAndView("printCommand",
"command",
"Warning: Duplicate Survey form submission attempt");
}
Such a handleInvalidSubmit method will work
for wizard forms as well as simple forms. If you don't override
this method, the default action is to process the duplicate
submission as if it were not a duplicate.
Typically, one sets a business object as the model for a view to
be presented to a user after execution of a controller. That
view may contain a form or even just a parameterized command.
Frequently, the intention is that only some of the fields of the
business object should be updated from the view. For example,
parts of a User bean may be presented in a
HTML form to the user so that the user can update his or her
preferences. If your User bean design
includes a privileges property that defines
the user's access privileges, then you certainly would not want the user to be able to set
such a property from the user preference form. However, a
malicious user can easily supply values for fields or properties
that do not exist on the form but which he/she guesses or
deduces might exist in the business object that is the model for
the form. The default strategy for handling forms (in most web
frameworks, not just Spring) is to copy all
parameters in the request into corresponding properties
(including nested properties) in the model bean. If you are not
careful, you could end up with a very serious security flaw in
your system that could allow a hacker entry into your system or
the ability to crash your system at will.
One crude way to avoid this problem is to have a special form or value bean that just has a subset of the properties of your model bean, namely the properties you want to allow to be set, and use that instead as the model bean and, on receipt of this form bean, copy all its properties into corresponding properties of the true model bean. This strategy, however, results in a proliferation of trivial classes which must match their master model classes and have no real purpose other than trivial data transfer: Avoid this antipattern
Spring provides a much better mechanism for handling this
problem: within any controller that extends
BaseCommandController, and that includes all
the command, form and wizard controllers, you can override the
initBinder method to apply some
initialisation to the Binder object that
extracts the parameters from a servlet request and matches them up
and assigns them to the corresponding properties of the model
bean. This Binder object will be of class
ServletRequestDataBinder for JSP/JSTL views,
which has a setAllowedFields method which
takes an argument which is an array of
Strings which are the names of the properties
of the model that the Binder is allowed to set.
We can modify our example application to see this in action.
First we modify the PrintParamCommand to add
an extra property that should not be set from any web form:
package sprMVCFull.model;
public class PrintParamCommand
{
private String paramName = null;
private String notAllowed = null; // Only to test setting of dissallowed fields
public PrintParamCommand()
{
}
public String getParamName()
{
return paramName;
}
public void setParamName(String paramName)
{
this.paramName = paramName;
}
public String getNotAllowed()
{
return notAllowed;
}
public void setNotAllowed(String notAllowed)
{
this.notAllowed = notAllowed;
}
public String toString()
{
return (notAllowed == null) ? paramName: paramName + " " + notAllowed;
}
}
Next we modify the PrintParamController class
to call the modified toString() method so
that we can see both properties. We only need to change the line
that returns the ModelAndView object to:
return new ModelAndView("printCommand", "command", (PrintParamCommand)command);
Finally we simulate a hacker setting the extra parameter: deploy
and start the web application. Click on the
PrintCommand(Link) link or the
PrintCommand(Button) button. The application
should go to the printCommand view and
correctly show that only the paramName
property has been set. Also, in the address field of the
browser, you should see a line that looks something like
"http://localhost:8080/printCommand.html?paramName=Button".
To the end of that field, add the text (without quotes or extra
spaces) "¬Allowed=hacked!" and enter
this modified address. The result should show that the
notAllowed property has indeed been set even
though you had not intended it to be.
To fix it, add the following code to the
PrintParamController class:
private static final String[] ALLOWED_FIELDS = {"paramName"} ;
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
throws Exception
{
binder.setAllowedFields(ALLOWED_FIELDS);
}
Now if you try the same experiment, you should find that the
notAllowed property is not set.
![]() | Important |
|---|---|
You should always set the allowed fields for a controller with a model object. It might seem unnecessary if all the properties of the model object are supposed to be set by the form. However, at some stage later in the lifetime of the application, that model object may have other properties added to it that should not be vulnerable to setting by the form — and, at that point, it is very easy to overlook the necessity of adding the appropriate restriction. | |
[AppFuse] The AppFuse Project . https://appfuse.dev.java.net/ .
[SRM] The Spring Reference Manual . http://www.springframework.org/docs/reference/index.html .
[SWS] The Spring Web Site . http://www.springframework.org/ .
[JHA05] Professional Java Development with the Spring Framework . Wrox. 2005. 0764574833. Due for publication 31st May 2005 .
[Raible04] Spring Live . SourceBeat. 2004. 0974884375. The electronic version which can be purchased at http://www.sourcebeat.com/TitleAction.do?id=7 includes updates for a year. .