Spring MVC

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 Controllers. 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 Controllers.

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.

Spring Framework Diagram

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:

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:

  1. ModelAndView processFormSubmission(HttpServletRequest request,
  2. HttpServletResponse response,
  3. Object command,
  4. 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:

  1. protected abstract ModelAndView showForm(
  2. HttpServletRequest request,
  3. HttpServletResponse response,
  4. 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:

  1. public class Fruit {
  2. private static Map instanceMap = new HashMap();
  3.  
  4. public static final Fruit APPLE = new Fruit(0, "Apple");
  5. public static final Fruit ORANGE = new Fruit(1, "Orange");
  6.  
  7. private long id;
  8. private String name;
  9.  
  10. private Fruit(Long id, String name) {
  11. this.id = id;
  12. this.name = name;
  13. instanceMap.put(new Long(id), this);
  14. }
  15. ......
  16. }

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:

  1. private class FruitSupport extends PropertyEditorSupport {
  2. public String getAsText() {
  3. Object value = getValue();
  4. return value == null ? "" : ((Fruit)value).getId().toString();
  5. }
  6.  
  7. public void setAsText(String string)
  8. throws IllegalArgumentException {
  9. try {
  10. Long id = Long.valueOf(string);
  11. Fruit f = Fruit.getById(id);
  12. if (f == null) {
  13. throw new IllegalArgumentException("Invalid id for Fruit: " + string);
  14. }
  15. setValue(f);
  16. } catch (NumberFormatException ex) {
  17. throw new IllegalArgumentException("Invalid id for Fruit: " + string);
  18. }
  19. }
  20. }

The Command (Form) Class

We also need a JavaBean to represent the contents of the form (name and fruit):

  1. public class FormBean {
  2. private String name;
  3. private Fruit fruit = Fruit.APPLE;
  4.  
  5. .. getters and setters
  6. }

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:

  1. <servlet>
  2. <servlet-name>spring</servlet-name>
  3. <servlet-class>
  4. org.springframework.web.servlet.DispatcherServlet
  5. </servlet-class>
  6. <init-param>
  7. <param-name>contextConfigLocation</param-name>
  8. <param-value>/WEB-INF/classes/spring-servlet.xml</param-value>
  9. </init-param>
  10. <load-on-startup>1</load-on-startup>
  11. </servlet>
  12. <servlet-mapping>
  13. <servlet-name>spring</servlet-name>
  14. <url-pattern>*.htm</url-pattern>
  15. </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.

  1. <bean id="formController"
  2. class="com.ociweb.spring.MyFormController">
  3. <property name="commandName"><value>formBeanId</value></property>
  4. <property name="commandClass">
  5. <value>com.ociweb.spring.FormBean</value></property>
  6. <!-- <property name="validator"><ref local="MyValidator"></ref></property> -->
  7. <property name="formView"><value>form</value></property>
  8. <property name="successView"><value>form</value></property>
  9. </bean>
  10.  
  11. <bean id="urlMapping"
  12. class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  13. <property name="mappings">
  14. <props>
  15. <prop key="/form.htm">formController</prop>
  16. </props>
  17. </property>
  18. </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:

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.

  1. public class MyFormController extends SimpleFormController {
  2. public MyFormController() {
  3. }
  4.  
  5. protected void initBinder(HttpServletRequest request,
  6. ServletRequestDataBinder binder)
  7. throws Exception {
  8. binder.registerCustomEditor(Fruit.class, new FruitSupport());
  9. }
  10.  
  11. protected Map referenceData(HttpServletRequest request)
  12. throws Exception {
  13. Map retval = new HashMap();
  14. retval.put("fruits", Fruit.getAll());
  15. return retval;
  16. }
  17.  
  18. protected ModelAndView onSubmit(
  19. HttpServletRequest request,
  20. HttpServletResponse response,
  21. Object command,
  22. BindException errors)
  23. throws Exception {
  24.  
  25. FormBean form = (FormBean)command;
  26.  
  27. // Act on form submission (e.g. write to database)
  28.  
  29. // Redirect back to form
  30. // This will ensure referenceData is set, etc.
  31. // This is needed when submit action fails despite success of earlier validation.
  32.  
  33. return showForm(request, response, errors);
  34. }
  35. }

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.

  1. <form method="post">
  2. <spring:bind path="formBeanId.name">
  3. <br/>Name : <input name="name" value="<c:out value="${status.value}"></c:out>"><br>
  4. <br/>
  5. </spring:bind>
  6.  
  7. <spring:bind path="formBeanId.fruit">
  8. Favorite Fruit:
  9. <select name="fruit">
  10. <c:forEach var="fruitType" items="${fruits}">
  11. <option value="<c:out value="${fruitType.id}"></option>"
  12. <c:if test="${fruitType.id == status.value}"> selected="selected"</c:if>
  13. >
  14. <c:out value="${fruitType.name}"></c:out>
  15. </option>
  16. </c:forEach>
  17. </select>
  18. </spring:bind>
  19. <input type="submit" name="Submit" value="Submit">
  20. <br/><br />
  21. </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

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret