Index page ★ Personal ~ Photography ~ Mathematics ~ Food ~ Français ~ Computing ~ Music
Introduction / Rants and Raves • Spring MVC for form validation • RISC OS on the Bush IBX-100
Index page ★ Personal ~ Photography ~ Mathematics ~ Food ~ Français ~ Computing ~ Music
Introduction / Rants and Raves • Spring MVC for form validation • RISC OS on the Bush IBX-100
Spring can be a powerful tool when developing web applications. However, at first impression it seems to be the kind of tool which is “jack of all trades, master of none”. This is far from the truth, though. In fact, Spring consists of a number of loosely-coupled components which can be used separately, or together. All are designed for integration with other popular tools such as Struts, Hibernate, Tapestry, and many others.
This document is concerned with using Spring together with JSPs and the JSTL to implement the MVC pattern, in particular for validation of forms. It details the problems I had getting my head around the concepts and finding out the best approaches.
There are a number of other useful documents on the web regarding Spring’s MVC framework. This document is intended to complement them, rather than being “just another tutorial” for Spring MVC.
The “traditional” approach to form validation with JSP is either to perform all of the processing and presentation within one JSP (this is very much frowned upon these days), or to use a JSP for presentation, and a helper bean to perform the relevant processing. If the JSTL is used in the JSP, then the Java code (in the bean) can be completely separated from the tag-based JSP page. However, when used for form processing, this model can lead to messy code in the bean, as it has to perform several functions (validation, committing the final data to a database, etc.), and also leaves control flow in the JSP file, as it has to call the bean’s methods, if the form has been validated it has to forward to the next page, and so on. A typical data flow through this model is described below.
Spring’s framework, on the other hand, advocates the use of servlet beans to separate clearly the display of the form and errors from the provision of the form’s data, validation, and the final action on the validated data.
To process a form using the traditional method, the following flow of data needs to occur:
This is clearly not a very elegant approach. There is quite a lot of decision logic in the JSP page, and current thinking is that JSPs should be used only for presentation. Also, the helper bean is fairly complex, and performs several functions. It could be split into several beans, of course, for validation, supply of default values, etc. but this still doesn’t solve the fundamental problem of having the JSP as the main point in the control flow loop.
When using Spring’s MVC implementation, the data flow is somewhat different, as follows:
This is a much nicer approach with a very clear separation of roles. The JSP page only needs to deal with presentation. Its only control flow statements are used to display error messages (JSTL tags of the form <c:if test="${status.error}"> ... </c:if>, etc.) An additional advantage of using Spring is that errors can very easily be bound to certain form fields, in addition to a global list of errors being available. Binding to specific fields without Spring is non-trivial.
Before you can create the controller and validator servlet beans, several configuration files need to be set up. Note that all of the notes given here assume the use of a Servlet 2.4 compatible container, such as Tomcat 5.0 or 5.5. If you are using a container which only meets the Servlet 2.3 or earlier specifications (such as Tomcat 4), then you will need to make the necessary adjustments.
web.xmlThe WEB-INF/web.xml file is the main configuration file for any web application, and is where filters, servlets, etc. are set up. You will need to add a servlet, along with a servlet-mapping section. The following minimal example is based on a servlet called SpringTest (defined in a file called SpringTest-servlet.xml, detailed shortly). The servlet-mapping section defines what file extensions will be mapped to the servlet.
<web-app id="SpringTest" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>SpringTest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringTest</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
The Controller and Validator Servlet classes need to be configured in a file called SpringTest-servlet.xml (replace SpringTest with whatever you decided to call your servlet in web.xml). To understand this file, you will need a combination of the well-documented DTD (available at http://www.springframework.org/dtd/spring-beans.dtd), and Spring’s API (available at http://www.springframework.org/docs/api/index.html).
This file is used to set up the various Java classes which make up the servlet. For each one, various properties can be set, the details for which can be seen in the API for the class in question. The following example is what I’ve used, with <!-- comments --> for some of the properties which aren’t so obvious. Note that the SpringTestValidator class is an implementation of the Validator interface, while the SpringTestController class is a subclass of the SimpleFormController class. The “command object” refers to the bean which will be used to store the actual data from the form. For each field, it needs a getter and setter, defined in the standard way.
The urlMapping bean is used to map specific URLs to particular servlet beans. The viewResolver bean automates the process of finding the correct JSP file, and avoids having to hardcode the JSP filename in the controller servlet bean.
<?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="springTestValidator" class="SpringTestValidator"/>
<bean id="springTestController" class="SpringTestController">
<property name="sessionForm"><value>true</value></property> <!-- Keep command object throughout session? -->
<property name="commandName"><value>commandBean</value></property> <!-- How it’s referenced in the JSP -->
<property name="commandClass"><value>DataBean</value></property> <!-- Its class -->
<property name="validator"><ref bean="springTestValidator"/></property>
<property name="formView"><value>testFormPage</value></property>
<property name="successView"><value>testSuccessPage</value></property>
</bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/testPage.html"><ref bean="springTestController"/></entry>
</map>
</property>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
<property name="suffix"><value>.jsp</value></property>
</bean>
</beans>
The easiest way to write a controller servlet bean for backing a form is to extend the SimpleFormController class. Recall that many of the properties of this bean were set up above, in the SpringTest-servlet.xml file. If you want to provide a default backing object for the form, then the formBackingObject() method should be overwritten. The doSubmitAction method will be called after a successful form validation, and passed the final copy of the command object. This is where the object should be written to the database, or whatever you’re going to do with it. A simple example of this controller servlet bean is as follows.
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.mvc.SimpleFormController;
public class SpringTestController extends SimpleFormController
{
protected final Log logger = LogFactory.getLog(getClass());
public Object formBackingObject(HttpServletRequest request) throws ServletException
{
DataBean backingObject = new DataBean();
/* The backing object should be set up here, with data for the initial values
* of the form’s fields. This could either be hard-coded, or retrieved from a
* database, perhaps by a parameter, eg. request.getParameter(“primaryKey”)
*/
logger.info("Returning backing object");
return backingObject;
}
public void doSubmitAction(Object command)
{
DataBean givenData = (DataBean)command;
/* The givenData object now contains the successfully validated data from the
* form, so can be written to a database, or whatever.
*/
logger.info("Form data successfully submitted");
}
}
The validation servlet bean is equally easy to write. In this case, the Validator interface needs to be implemented, but it only has two methods. The supports() method is used to check whether the validator supports a given class, and the validate() method is used to actually validate an object of the supported class. A rejection is performed by adding one or more ObjectError instances to the Errors object which is passed through to this method. A rejection of the entire form is performed with the reject() method, and specific fields are rejected using the rejectValue() method. The following skeleton example should give the general idea.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
public class SpringTestValidator implements Validator
{
private final Log logger = LogFactory.getLog(getClass());
public boolean supports(Class givenClass)
{
return givenClass.equals(DataBean.class);
}
public void validate(Object obj, Errors errors)
{
DataBean givenData = (DataBean)obj;
if (givenData == null)
{
errors.reject("error.nullpointer", "Null data received");
}
else
{
logger.info("Validating form");
/* Test givenData’s fields here */
if (givenData.something())
{
errors.rejectValue("field", "error.code", "Default error message");
}
}
}
}
Once the servlet beans have been written, most of the work has been done. All that remains is to write the JSP file to output the form itself. This is done using the usual JSP, (X)HTML and JSTL tags, as can be seen in the following example, with the addition of a few special Spring tags. (X)HTML tags are shown in black, while JSP/JSTL/Spring tags and EL expressions are shown in red.
Clearly the <spring:bind> tag is very important. It is used to connect one of the form’s input fields with the corresponding field in the command object (bean). Within this tag, the equally important status object is available. It is of type BindStatus, and allows access to a number of objects which are relevant to the field in question. Principally, status.value is used to get the current value of the field. This will either have been set up in the controller bean servlet (in the formBackingObject() method), or will be the previously submitted value if the form has already been filled in, submitted, and rejected by the validator bean servlet. In addition to the field’s value, the boolean status.error flag is set depending on whether any errors have been linked to this field. If they have been, then status.errorMessages will contain a list of error messages linked to the field, localised if necessary. status.errorMessage contains the first error message, and can safely be used if you are expecting a maximum of one error per form field.
In addition to this tag, the <spring:hasBindErrors> tag is used to access all of the errors on the form, including those which are not tied to specific fields. It contains the errors object, of type Errors. This provides access to various lists and statistics, for example errors.errorCount & errors.allErrors, the total number of errors and a list containing them respectively, and errors.globalErrorCount & errors.globalErrors, similar objects for those errors which aren’t tied to specific fields. NB. the errors.allErrors and errors.globalErrors are lists of ObjectError objects, not formatted error messages as above. Therefore, when iterating over this list using the <c:forEach> tag, error messages should be output using the <spring:message> tag as demonstrated in the code below. This ensures that the localised version of the message is output if it exists, and the standard message if it doesn’t. This is the same as what the status.errorMessages in the <spring:bind> detailed above does.
Also of note is the action field of the <form> tag. If no action is specified, then the submission of the form will automatically go back to the servlet that supplied it, which is what we want. However, if no action is specified and one or more default attributes have been passed through to the servlet using get (ie. the page was loaded with an address similar to .../testPage.html?fieldOne=1234), then problems can arise. This is because the same URL, with the get data on the end, will be used to submit the form. The form data will be sent using post, though, so the servlet may receive both sets of data which can lead to unpredictable results. If the form’s action is specified, then this problem won’t arise.
<%@ page language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head><title>Spring Test</title></head> <body> <h1>Spring Test</h1> <form method="post" action="testPage.html"> <spring:bind path="commandBean.fieldOne"> <input type="text" name="fieldOne" value="${status.value}" /> <c:if test="${status.error}"><p>There was an error with your previous input.</p></c:if> </spring:bind> <spring:bind path="commandBean.fieldTwo"> <input type="text" name="fieldTwo" value="${status.value}" /> <c:if test="${status.error}"> <p>These were your errors:</p> <ul> <c:forEach var="errorMessage" items="${status.errorMessages}"> <li>${errorMessage}</li> </c:forEach> </ul> </c:if> </spring:bind> <spring:hasBindErrors name="commandBean"> <p>There were ${errors.errorCount} error(s) in total:</p> <ul> <c:forEach var="errMsgObj" items="${errors.allErrors}"> <li> <spring:message code="${errMsgObj.code}" text="${errMsgObj.defaultMessage}"/> </li> </c:forEach> </ul> </spring:hasBindErrors> <input type="submit" value="Update" /> </form> </body> </html>
When errors are thrown in the validator servlet bean, using the reject() or rejectValue() methods, then both a default error message and an error key need to be specified. This is useful if later on you want to translate your web application into a different language. Instead of going through all of the java source files and rewriting them with the translations, you can define a messages file containing a map from error keys to error messages. This is defined using a servlet bean definition similar to the following in your -servlet.xml file:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename"><value>messages</value></property> </bean>
In this case, messages should be defined in files messages.properties (default), messages_en.properties (English), etc. The files should be placed in the WEB-INF/classes directory, and each should contain lines of the form key=Message.
Javid Jamae’s tutorial (Simplify Your Web App Development Using the Spring MVC Framework) is an excellent introduction to Spring’s MVC framework, however it has a couple of errors, and there is some room for improvement. These are detailed here.
logon.jspAs provided, the logon.jsp file doesn’t work correctly with regard to error messages, at least with Spring 1.1.4. An attempt is made to access the object status.errorMessage from within a <spring:hasBindErrors> tag, where it is unavailable. The easiest way to fix this is to move the sections <font color="red"><core:out value="${status.errorMessage}"/></font> into the preceeding <spring:bind> tags, and delete the <tr> sections containing the <spring:hasBindErrors> tags.
If you are using a Servlet 2.4 compatible container, such as Tomcat 5.0 or 5.5, then you should use a Servlet 2.4 specification web.xml file, rather than the 2.3 specification example given. This will allow you to use JSTL tag libraries without messing about with TLD files, and enable EL (the expression language) by default. If you use a 2.3 specification web.xml file, then it will be disabled by default.
In fact, the file given is already compatible with the 2.4 specification. All that needs to be done is to change the header, by replacing the old DTD reference and opening tag:
<?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>
with the new XMLSchema opening tag:
<web-app id="tradingapp" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
Once you have switched to the new specification web.xml file, there is no need to copy any TLD files across or add them to the web.xml file. However, the include.jsp file will need to have the correct URIs for the various tag libraries used, as follows:
<%@ page session="false"%> <%@ taglib prefix="core" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="str" uri="http://jakarta.apache.org/taglibs/string-1.1" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
This article is copyright © Chris Sawer 2005, but is licensed under the terms of the Creative Commons Attribution 2.5 licence.
For more information visit http://creativecommons.org/licenses/by/2.5/