Spring MVC
By Paul Jensen, OCI Principal Software Engineer
October 2004
Introduction
The Spring
framework (http://spring.io) has recently gained popularity as an alternative to current J2EE programming practices. In fact, Spring
provides general functionality which is useful outside of J2EE, serving to promote good programming practices such as designing to interfaces, unit testing, and flexibility (e.g. avoiding dependencies, configurability).
At its core, Spring
is a system for assembling components via configuration files (typically XML). This simple concept has been termed Inversion of Control, the Hollywood Principle ("Don't call me, I'll call you"), and Dependency Injection. Basically, components do not resolve their dependencies themselves.
For example, consider a Data Access Object (DAO) that requires a database connection (or a factory to obtain it). Typically, the DAO will lookup this resource in JNDI or some other service. It will resolve its dependency by "pulling" it from somewhere, maybe even (gasp!) a singleton. There are various degrees of evil dependencies introduced by this approach. If a singleton or any static method is involved (yes, including a constructor), a hard dependency is created limiting reuse, flexibility, and testability of the DAO. Use of a service such as JNDI is more flexible, but still a dependency.
Now, consider a scenario where some outside force resolves the DAOs dependency by "pushing" the specific implementation into the DAO. The DAO now has no dependency on implementation, lookup service, etc. Specific environments (including tests) can inject their implementation details and the DAO is only dependent on the resource's interface. Spring
performs this "dependency injection". The Spring ApplicationContext
instantiates and connects objects as described in an XML configuration file. This is accomplished via the well-established and simple JavaBean standard; components need merely advertise their dependencies and configurable properties via standard set and get methods.
Spring
is sometimes called a lightweight container, but Spring
is actually quite large (with approximately 1000 classes/interfaces). The lightweight term refers more to its ability to integrate technologies with a minimum of dependencies (as opposed to many J2EE solutions which require implementation of specific interfaces).
Using the Dependency Injection framework as a foundation, Spring
adds support for many common aspects of application functionality including persistence, transaction control, AOP, error handling, and distributed computing. This article will only cover aspects of its web-based MVC framework. For information on other aspects of Spring
, see the references at the end of this article.
Spring MVC
Spring
employs a familiar Model-View-Controller architecture as seen in a variety of web-based frameworks, Struts being the most popular. The Front Controller
servlet receives all HTTP requests for the system and uses defined mappings to route to handlers. These handlers are typically implementations of the Controller
interface in Spring
, but may be any class in keeping with the spirit of the framework. In Spring
, the DispatcherServlet
serves as the Front Controller
, dispatching calls to other Controller
s. It performs several other tasks in the process of handling a request (all in a highly configurable manner), including mapping of views and specialized handling of exceptions. This article will only touch on these areas briefly, focusing on use of Controller
s.
The controller is analogous to the Struts Action. It processes requests and returns a ModelAndView
object that holds a Map representing the Model
(data for presentation) and a representation of a View
. The View
is defined by either a String
(which is resolved by a configurable ViewResolver
) or an instance of a View
(which is used directly). A null return indicates the controller has completed processing of the request and no additional View
is necessary.
Many controllers exist in the Spring
framework as illustrated in the diagram below.
The SimpleFormController
is the most frequently used of these Controller
classes. Despite its name, the SimpleFormController has a non-trivial lifecyle and related functionality - as implied by its deep inheritance hierarchy shown above. We will detail the functionality of the SimpleFormController
by discussing its base classes in turn.
AbstractController
provides low-level configuration and functionality before delegating request handling to a derived class' handleRequestInternal()
method. It generates caching headers matching the cacheSeconds property, rejects GET or POST requests according to configuration, and ensures a session exists if it is flagged as being required. Requests may also be synchronized on the session to prevent multi-threaded access by the same client. Note that Spring
is similar to Struts in that Controller
instances are reused. (Alternatively, controllers implementing ThrowawayController
are instantiated for each request.)
The BaseCommandController
maps request parameters to a configured command class (similar to Struts ActionForms but a simple JavaBean with no framework dependencies). A validator may be registered to check the contents of the Command
. This class does not define a handleRequestInternal()
, leaving the workflow to derived classes. It does define the protected final bindAndValidate()
which binds request parameters to the command object and validates it contents. Several hooks are provided to customize this process:
initBinder(request, binder)
- may be overriden to addPropertyEditors
to binder for handling special typesonBind(request, command)
- called after binding, but before validation. May be overridden to perform specialized mapping, correct mapping, etc.onBindAndValidate(request, command)
- called after validation. May be overridden to do additional validation, for example.
AbstractFormController
overrides handleRequestInternal()
to create separate workflows for handling GET and POST requests. This class refers to the command object (from BaseCommandController
) as a form object, so this terminology will be used below.
For a POST request, either a default instance of the form (command) object is created or a pre-existing one is retrieved from the session (and then removed). The derived AbstractWizardFormController
utilizes the session approach for multi-screen input. The session-scoped command object may also be used as a means to prevent duplicate form submissions by configuring the controller to require its presence (see javadocs for details). The initial command object may be defined by overriding formBackingObject()
.
Next, the form object is populated with the request parameters using BaseCommandController.bindAndValidate()
. AbstractFormController
does not define any of the corresponding abstract methods mentioned above (initBind
, etc.). Finally, the handling is delegated to the abstract method:
- ModelAndView processFormSubmission(HttpServletRequest request,
- HttpServletResponse response,
- Object command,
- BindException errors)
GET request handling starts by obtaining the command (form) object via formBackingObject()
. This may be overridden to provide an object populated with data from a database. If enabled, request parameters are then bound to the command (again, initBinder()
may specialize handling). Finally, handling is delegated to:
- protected abstract ModelAndView showForm(
- HttpServletRequest request,
- HttpServletResponse response,
- BindException errors) throws Exception;
The concrete class SimpleFormController
(at last!) provides a few additions to AbstractFormController
functionality. Form and success views may be configured - validation failure returns the user to the form view while successful validation and subsequent action lead to the success view. Typically, derived classes need only override one of several onSubmit()
methods to handle form submission (e.g. save to the database).
MVC Example
Now let's put some of this to use. We'll create a simple form in which the user enters their name and chooses their favorite fruit.
Fruit
The Fruit class is a fairly standard pre JDK 1.5 Java enumerated type:
- public class Fruit {
- private static Map instanceMap = new HashMap();
-
- public static final Fruit APPLE = new Fruit(0, "Apple");
- public static final Fruit ORANGE = new Fruit(1, "Orange");
-
- private long id;
- private String name;
-
- private Fruit(Long id, String name) {
- this.id = id;
- this.name = name;
- instanceMap.put(new Long(id), this);
- }
- ......
- }
Of course, this could just as well be stored in a database as a lookup table. Since these objects correspond to string value attributes for an HTML <OPTION
> tag, the main issue is mapping a drop-down to a server-managed list of objects.
To convert string-based references to enumerated values (and vice-versa), we define a specialized java.beans.PropertyEditor
:
- private class FruitSupport extends PropertyEditorSupport {
- public String getAsText() {
- Object value = getValue();
- return value == null ? "" : ((Fruit)value).getId().toString();
- }
-
- public void setAsText(String string)
- throws IllegalArgumentException {
- try {
- Long id = Long.valueOf(string);
- Fruit f = Fruit.getById(id);
- if (f == null) {
- throw new IllegalArgumentException("Invalid id for Fruit: " + string);
- }
- setValue(f);
- } catch (NumberFormatException ex) {
- throw new IllegalArgumentException("Invalid id for Fruit: " + string);
- }
- }
- }
The Command (Form) Class
We also need a JavaBean to represent the contents of the form (name and fruit):
- public class FormBean {
- private String name;
- private Fruit fruit = Fruit.APPLE;
-
- .. getters and setters
- }
You may have noticed that the last 3 classes have absolutely no dependency on Spring
, which emphasizes Spring's focus on flexibility and modularity. Now back to the framework...
Spring Configuration
Basic integration of the framework requires the addition of the DispatcherServlet
to the web.xml
deployment descriptor:
- <servlet>
- <servlet-name>spring</servlet-name>
- <servlet-class>
- org.springframework.web.servlet.DispatcherServlet
- </servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/classes/spring-servlet.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>spring</servlet-name>
- <url-pattern>*.htm</url-pattern>
- </servlet-mapping>
Note that all .htm
URLs will be routed to the Spring
servlet. The spring-servlet.xml
file defines mappings for the DispatcherServlet
to delegate to various handlers (here a Controller
) in addition to configuration of other aspects of the MVC architecture. The section below defines the Controller
, related classes, and its mapping to a URL and is representative of Spring
's syntax for wiring of components in XML configuration files.
- <bean id="formController"
- class="com.ociweb.spring.MyFormController">
- <property name="commandName"><value>formBeanId</value></property>
- <property name="commandClass">
- <value>com.ociweb.spring.FormBean</value></property>
- <!-- <property name="validator"><ref local="MyValidator"></ref></property> -->
- <property name="formView"><value>form</value></property>
- <property name="successView"><value>form</value></property>
- </bean>
-
- <bean id="urlMapping"
- class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
- <property name="mappings">
- <props>
- <prop key="/form.htm">formController</prop>
- </props>
- </property>
- </bean>
The instance of MyFormController
is given an id “formController
” and is referenced by that id in the urlMapping
bean to map the URL /form.htm
to the controller instance. Properties of MyFormController
are defined by similar links:
commandName
defines an id string for the command objectcommandClass
defines our form bean as an instance ofFormBean
formView
defines the view to invoke if form submission failssuccessView
defines the view to invoke if form submission succeeds
Note that formView
and successView
refer to the same view. This is atypical, but for this example we will never accept the submission anyway. Two tasks remain – define MyController
and the view.
Controller
The MyFormController
class is fairly brief and is presented below in its entirety. It extends SimpleFormController
and defines a default constructor to support Spring
internals (which by default utilize Class.newInstance()
). initBinder()
registers the PropertyEditor
to the binder object to support the Fruit type as discussed above. The referenceData()
method obtains all Fruit instances and places them in a map with a unique key. These values are transferred to the HttpServletRequest
by its superclass for use in the JSP (below).
Lastly, the onSubmit()
method processes the data from the form/command object. While a number of simpler onSubmit()
methods exist and could have been overridden, the full set of parameters (request, response, etc.) is needed in this case. The complexity lies in returning to the form page while maintaining the list of items provided by referenceData()
. On success submission of the form, this method is not called, resulting an empty list. A number of solutions are possible, but this seemed easiest for this example. (For the curious, the other possibilities I considered were to either add a validator that always fails or to call referenceData()
and insert the values myself. If there are others, I’d like to know.) In any event, in order to present the page in the same way a GET would, the showForm()
method is called.
- public class MyFormController extends SimpleFormController {
- public MyFormController() {
- }
-
- protected void initBinder(HttpServletRequest request,
- ServletRequestDataBinder binder)
- throws Exception {
- binder.registerCustomEditor(Fruit.class, new FruitSupport());
- }
-
- protected Map referenceData(HttpServletRequest request)
- throws Exception {
- Map retval = new HashMap();
- retval.put("fruits", Fruit.getAll());
- return retval;
- }
-
- protected ModelAndView onSubmit(
- HttpServletRequest request,
- HttpServletResponse response,
- Object command,
- BindException errors)
- throws Exception {
-
- FormBean form = (FormBean)command;
-
- // Act on form submission (e.g. write to database)
-
- // Redirect back to form
- // This will ensure referenceData is set, etc.
- // This is needed when submit action fails despite success of earlier validation.
-
- return showForm(request, response, errors);
- }
- }
JSP
Finally, the JSP page for /form.htm
defines the View
. Spring
provides easy integration for many different View options, including JSP, XSLT, Velocity, and FreeMarker. Views may be easily integrated with different handlers. With respect to JSP, Spring
supports only a few custom tags and only one, spring:bind
, is commonly used. The form.jsp
page illustrates its usage along with JSTL.
Essentially, the spring:bind
tag creates a local context in which the status
variable is associated with the binding of a variable referenced in the tag. status
contains the result of the last validation attempt, the value bound, and the identifier of the property.
- <form method="post">
- <spring:bind path="formBeanId.name">
- <br/>Name : <input name="name" value="<c:out value="${status.value}"></c:out>"><br>
- <br/>
- </spring:bind>
-
- <spring:bind path="formBeanId.fruit">
- Favorite Fruit:
- <select name="fruit">
- <c:forEach var="fruitType" items="${fruits}">
- <option value="<c:out value="${fruitType.id}"></option>"
- <c:if test="${fruitType.id == status.value}"> selected="selected"</c:if>
- >
- <c:out value="${fruitType.name}"></c:out>
- </option>
- </c:forEach>
- </select>
- </spring:bind>
- <input type="submit" name="Submit" value="Submit">
- <br/><br />
- </form>
Summary
This article only scratches the surface of the Spring
framework. Disregarding the MVC framework, Spring
offers many benefits at other layers of the system, including integration with JDO and Hibernate. As a general approach, it promotes and simplifies Test-Driven Development (TDD) which fosters many beneficial design and development practices.
Spring
is under very active development and tool integration is constantly improving. For example, planned enhancements include new or improved integration with JSF, JMX, and AspectJ. XDoclet support is available and there is also an Eclipse plugin. I encourage you to investigate the Spring
framework. I think you'll find it a useful tool in controlling the complexity of many applications.
"Now is the winter of our discontent made glorious Spring (sic)"
-William Shakespeare – Richard III
References
- [1] Spring Framework Home
http://www.springframework.org/ - [2] Book: Professional Java Development with the Spring Framework (by the Spring developers - Feb. 2005)
http://www.wiley.com/WileyCDA/WileyTitle/productCd-0764574833.html - [3] Matt Raible Weblog (Spring Live author)
http://jroller.com/page/raible
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.