Building web applications with Spring Web MVC
3.3 Serving and processing forms
Now that you’ve seen how to create a simple POJO controller, it’s time to get a little more adventurous and implement form serving and processing functionality. Let’s pretend you create a form to allow the user to nominate a member for an award.
(Yeah, we’re making this example up as we go.) Although it’s entirely possible to mix and match form methods with non-form methods on a single controller, let’s create a new controller because showing rosters and roster members isn’t closely related to nominating members for awards.
Before creating your new controller, you’ll need a form bean. One of the nice features of Spring Web MVC is that if it makes sense to do so, you can use your domain objects as form beans. Let’s see what’s involved with that.
3.3.1 Using domain objects as form beans
To add a form, the first thing you need to do is create a bean to store the form data.
This is called a form bean or form-backing bean. There are a couple of different approaches to doing this, and they aren’t mutually exclusive.
Listing 3.6 /WEB-INF/jsp/roster/member.jsp, a member details page
Member exposed as ${member}
B
A best practice for controllers
With Spring Web MVC before Spring 2.5, the way you bundled controller methods to-gether was dictated more by the classes in the Controller hierarchy than it was by which methods were closely related. With POJO controllers that’s no longer the case.
A best practice is to bundle closely related methods together in a single controller, and to put unrelated methods on other controllers.
One approach is to use your domain objects as your form beans. This works well when there’s a close match between the fields you require on the form and the prop-erties your domain model has, as is often the case. The advantage of this approach is that you can avoid creating separate but parallel sets of classes for your domain objects and your form beans, which keeps the clutter down. (With Struts 1, for instance, it was common to have parallel sets of classes like this.)
Another approach is to implement your form bean separately from your domain model. This can make sense when the domain model has properties for which there aren’t corresponding form fields, when the form has fields for which there aren’t cor-responding domain object properties, or both. A good example (which you’ll see in chapter 4, even though there you use the domain object as a form bean after all) is a user registration form. Most registration forms have a “confirm password” field, and many have CAPTCHA fields, but neither of those is properly part of the domain model.
Similarly, the domain model often has properties such as confirmed, enabled, and so forth. Obviously you wouldn’t include those as form fields.
When using this second approach, you have to pay attention to how you handle the extra fields on both sides. If the form has extra fields, like a “confirm password” field, you can declare those as separate @RequestParam parameters as you did in listing 3.4.
If the domain model has extra properties, such as confirmed or enabled, you’ll need to make sure users can’t bind extraneous HTTP parameters to those properties. That involves creating a form-binding whitelist. See section 3.3.6 for more information.
Again, the approaches aren’t mutually exclusive, meaning you can use domain objects to back forms in certain cases and dedicated form beans in other cases.
That’s enough philosophizing for now. In this case, the Member class is exactly what you’d want from a form bean, so you’ll use it as a form bean.
Is it really OK to use domain objects as form beans?
Even in cases where there’s not an exact match between form beans and domain objects, it’s a judgment call and a matter of architectural sensibilities whether you reuse your domain objects for your form.
Some prefer not to do it because they (reasonably) draw a strong architectural distinction between form beans and domain objects. Struts 1, for instance, works like this: the framework enforces a clean separation between domain objects and form models.
Others, your authors included, don’t mind a little architectural impurity if we can avoid having two parallel sets of strongly similar classes (which carries its own costs). If our domain objects and form beans mostly overlap, we’re willing to accept (for in-stance) JPA annotations in our form bean as the cost of having a single class.
Spring Web MVC provides this option by design. Reasonable minds will differ as to its architectural merits. In practice we find the option useful and not confusing.
Let’s build the new controller.
3.3.2 Adding a controller
The new form will allow the user to nominate a member for an award. The control-ler won’t do anything other than show some log output and forward to a “thanks”
page. You just want to see how to set up a form. The following listing contains the new form controller.
// Source project: sip03, branch: 02 (Maven Project) package com.springinpractice.ch03.web;
Logger.getLogger(NomineeController.class);
private String thanksViewName;
public void setThanksViewName(String thanksViewName) { this.thanksViewName = thanksViewName;
}
@RequestMapping(method = RequestMethod.GET) public Member form() { return new Member();}
@RequestMapping(method = RequestMethod.POST) public String processFormData(Member member) { log.info("Processing nominee: " + member);
return thanksViewName;
} }
The new controller is dedicated to serving and processing a form. You define a setter so you can inject a logical view name for a “thanks” page after the user submits a nom-ination
B
. It’s probably obvious why you don’t want to return something like / WEB-INF/jsp/nominee/thanks.jsp: that doesn’t give you a good separation between the controller and the view. But it may be less obvious why you don’t return a logical view name, say thanks. You’ll see the answer to that when we discuss the redirect-after-post pattern, but for now take it on faith that even with the view name it often makes sense to keep the controller and view separate.You have two methods. One is marked as handling GET requests and the other as handling POSTs. In the GET handler
C
, you return an empty form bean so the HTML form fields have something to bind to. In this case you aren’t prepopulating the Mem-ber form bean, but sometimes it’s useful to do that kind of thing.Listing 3.7 NomineeController.java, a simple form controller
Injects view
Whether or not you prepopulate the form bean with data, by returning it from the method you’re placing it on the Model under the generated attribute name member
D
. If you wanted to use another attribute name, such as nominee, you would do something like this:@RequestMapping(method = RequestMethod.GET) public void form(Model model) {
model.addAttribute("nominee", new Member());
}
The result is the same, except that the form bean’s attribute name is nominee instead of member.
At any rate, let’s be happy with the attribute name member and return a Member. But the choice is yours. This is yet another example of how flexible Spring Web MVC is.
Now let’s look at the POST handler. You include an annotation marking it as such
E
, and then you have a method signature that takes a Member and returns a StringF
. Once again, the signature you use is dependent on your need. By taking a Member parameter, the submitted form data will be automatically bound to a Member bean, the bean will be placed on the model as an attribute under its generated name (member), and the bean will be passed into the method itself. But as with return types, you aren’t forced to use the generated name here: you can use the @ModelAttribute annotation5 to select a dif-ferent name. Play around with the following, adding and removing the @ModelAttrib-ute annotation, to see how it works:@RequestMapping(method = RequestMethod.POST) public String processFormData(
@ModelAttribute("nominee") Member member, Model model) { log.info("Processing nominee: " + member);
Map map = model.asMap();
log.info("model[member]=" + map.get("member"));
log.info("model[nominee]=" + map.get("nominee"));
return thanksViewName;
}
For the following discussion, assume that you’re using the code from listing 3.8 rather than the modified version. We wanted to give you a nice way to understand what’s going on with the form bean parameter, as well as exposure to the @ModelAt-tribute annotation.
5 The fully qualified class name is org.springframework.web.bind.annotation.ModelAttribute.
Prepopulating form beans
The most common case of prepopulating form beans arises in validation scenarios:
when the user enters invalid form data, you usually want to re-present that invalid data in the form so the user can correct it, rather than making them reenter the data from scratch. There are other examples too. You might prepopulate a request for in-formation (RFI) form bean with location data based on the user’s IP address.
At
G
you return the view name. Any time the return type is a String, Dispatch-erServlet assumes that the return value represents a logical view name.You’ll need a couple more JSPs: one to display the form and one to thank the user for submitting the form.
3.3.3 Adding a form JSP and a “thanks” JSP The following listing shows the form.
<%-- Source project: sip03, branch: 02 (Maven Project) --%>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>Nominate a member for the award</title>
</head>
<body>
<h1>Nominate a member for the award</h1>
<form:form modelAttribute="member">
<div>First name: <form:input path="firstName"/></div>
<div>Last name: <form:input path="lastName"/></div>
<div><input type="submit" value="Submit"></input></div>
</form:form>
</body>
</html>
The form is modest and looks a lot like a normal HTML form. The main difference is that you declare the Spring form tag library
B
, and you’re using that to represent the form and its inputs. The primary advantage of using the form tag library is that it pro-vides binding form inputs. If you were to prepopulate the Member bean with data, that data would automatically be rendered in the corresponding text field. (Try it out if you like.)At
C
you bind the form to the member attribute that the controller placed on the Model. This gives you a way to interpret input paths (discussed in a moment) as bean properties. If you don’t specify an explicit value for modelAttribute, the default form bean attribute name is command, which isn’t descriptive. You should probably always set a modelAttribute explicitly.At
D
you have a text field that binds to member.firstName. There’s one for the last name too. The form tag library has tags for all the standard HTML input controls, although you’re using only text fields here.Finally you have a submit button
E
. There’s no tag from the tag library for this because there isn’t anything for the submit button to bind to; that is, the submit button doesn’t have a corresponding bean property. So you use a normal HTML sub-mit button.The next listing shows a basic “thanks” page.
Listing 3.8 /WEB-INF/jsp/nominee/form.jsp
<%-- Source project: sip03, branch: 02 (Maven Project) --%>
<html>
<head>
<title>Thanks</title>
</head>
<body>
<h1>Thanks</h1>
<p>Thanks for nominating ${member}.</p>
</body>
</html>
The only reason we’re looking at this is that it shows you that the Member form bean passed into processFormData() lives on the Model under the member attribute name.
As you can see, adding a form is more involved than actions that grab data and dis-play it. And we haven’t yet touched whitelisting and validation. But all things consid-ered, it’s still fairly straightforward. The framework handles form/bean binding automatically, and you define the controller methods to use just what you need and nothing else.
Let’s finish the form (for the moment) by updating the application context.
3.3.4 Updating the application context
You need to make only one change to the main-servlet.xml application context file.
Add the following bean, and you’re set:
<bean name="/nominee/*"
class="com.springinpractice.ch03.web.NomineeController"
p:thanksViewName="nominee/thanks"/>
It’s time to try the new form. Point your browser to http://localhost:8080/sip/main/
nominee/form.do. You should get the nomination form. When you complete the form and submit it, you should get the “thank you” message, complete with the nomi-nee’s name.
The form basically works, but it’s not done yet. You need to address important usability and security issues. Let’s look at those.
3.3.5 Adding redirect-after-post behavior
One common pattern to use with web-based forms is called redirect-after-post. The idea is that when a user submits a form via HTTP POST, it’s nice to force a redirect to mini-mize the likelihood of a double submit, to avoid browser warnings when the user clicks the back button, to make the resulting page easier to bookmark, and so forth.
To do this, prepend redirect: to the logical view name. This will cause RedirectView to kick in, and the browser will request whatever page you tell it to request.
This illustrates a good reason for using dependency injection to set logical view names. Presumably controllers shouldn’t know whether they’re issuing forwards or redi-rects. By keeping the view names configurable, you achieve controller/view separation.
Listing 3.9 /WEB-INF/jsp/nominee/thanks.jsp
Now let’s look at a security issue you need to address when working with forms.
3.3.6 Adding form-binding whitelisting
In July 2008, Ounce Labs6 discovered a security vulnerability in Spring Web MVC related to automatic form binding. Because Spring automatically binds HTTP parame-ters to form bean properties, an attacker could conceivably bind to properties that weren’t intended for binding by providing suitably named HTTP parameters. This might be a real problem in cases where a domain object is serving as the form-backing bean, because domain objects often have fields that are suppressed when used in form-backing scenarios, as we discussed in section 3.3.1.
To address this problem, you can define explicit whitelists in your controllers. You can do this using so-called @InitBinder methods. You’ll need one for each form. (If your controller has two forms, it needs two separate @InitBinder methods.)
Here’s an example you’ll see later in the book. You have a controller that handles two forms: one to allow users to subscribe to a mailing list using a form called sub-scriber, and one to allow them to unsubscribe using a form called unsubscriber. You use these @InitBinder methods to whitelist the form fields:
@InitBinder("subscriber")
public void initSubscriberBinder(WebDataBinder binder) { binder.setAllowedFields(new String[] {
"firstName", "lastName", "email"
});
}
@InitBinder("unsubscriber")
public void initUnsubscriberBinder(WebDataBinder binder) { binder.setAllowedFields(new String[] { "email" });
}
If there’s only one form, you don’t have to provide an explicit annotation value. But because you have two forms, you need to specify subscriber or unsubscriber to let Spring know which binder to initialize.
Even after initializing the binders, you aren’t finished. You have to verify that the whitelist has been respected each time the user submits a form. One simple way to do this is to define a helper class with a static verification method and call that from your form-processing methods. Here’s a sample implementation:
public static void verifyBinding(BindingResult result) { String[] suppressedFields = result.getSuppressedFields();
if (suppressedFields.length > 0) { throw new RuntimeException(
"Attempting to bind suppressed fields: " +
StringUtils.arrayToCommaDelimitedString(suppressedFields));
} }
6 Ounce Labs was acquired by IBM in 2009.
A more sophisticated way to do this might be to define an aspect that automatically applies the verification to all form-processing methods, although you don’t do that here.
See recipe 4.1 for more information on whitelisting form bindings.
3.3.7 Adding form validation
When users submit form data, you typically want to validate it before accepting it for processing. For example, in the present case you’d want to make sure that the fields aren’t empty, that they aren’t too long, that the e-mail field looks like a real e-mail address, and so forth. The preferred approach will eventually be to use JSR 303 (Bean Validation) to define annotation-based validation semantics on objects requiring vali-dation. But JSR 303 isn’t ready at the time of this writing, so the preferred approach until then is to use Hibernate Validator. Please see recipe 4.2 for a detailed example of how to perform annotation-based form validation in Spring.
Now that you’ve toured some of the capabilities Spring provides for implementing web-based MVC applications, let’s look more carefully at configuration.
3.4 Configuring Spring Web MVC: web.xml
In sections 3.2 and 3.3 you created a toy application, and then we discussed in some detail how it works. We focused on the programming model rather than the configura-tion model, but now it’s time to address configuraconfigura-tion. Understanding Spring Web MVC configuration amounts to understanding how to configure the DispatcherServlet.
There are two levels of DispatcherServlet configuration. First, because it’s a serv-let, you declare one or more DispatcherServlet instances and their corresponding servlet mappings inside web.xml. Second, each DispatcherServlet instance has its own application context, and by configuring that you configure the DispatcherServ-let itself. In this section we’ll look at web.xml; in the following sections we’ll look at the much more involved matter of configuring the servlet’s application context.
DispatcherServlet is only a servlet, so at a certain level of abstraction there’s no dif-ference between configuring DispatcherServlet and configuring other servlets. The following listing shows a perfectly simple and valid DispatcherServlet configuration.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener </listener-class>
Listing 3.10 Simple DispatcherServlet configuration in web.xml
Loads root app context
B
</listener>
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main/*</url-pattern>
</servlet-mapping>
</web-app>
This is a minimal DispatcherServlet configuration. You load a root application con-text from the default location, /WEB-INF/applicationContext.xml
B
. Then you create the DispatcherServletC
. Because you haven’t specified a location for the servlet’s dedicated application context configuration, DispatcherServlet assumes that it exists at /WEB-INF/main-servlet.xml; the general pattern for the default location is /WEB-INF/[servlet-name]-servlet.xml. Finally, you specify the requests that you want the DispatcherServlet to serviceD
.When DispatcherServlet creates its application context, it uses the root app
When DispatcherServlet creates its application context, it uses the root app