Mastering Spring MVC

Enjoy Spring-based Web development with the Spring MVC module

Model-View-Controller Web frameworks for Java are plentiful, and most of them integrate with Spring, but the tightest integration can be found in Spring's own MVC module. Get started with Spring MVC in this introductory article by Steven Haines, who begins with a history of the Model-View-Controller paradigm in Java Web development. He then introduces the Spring Framework and explains how it leverages the concepts of Spring, including dependency injection. Finally, Steven guides you through a programming tutorial that demonstrates the core features of Spring MVC in an easy-to-follow, hands-on format. Level: Beginner

In the ongoing evolution of Java EE technologies, the Spring Framework has emerged as a top choice for the middle, or business tier of enterprise application development. Spring's lightweight paradigm for developing enterprise applications has won it a large following that today rivals that of the standard Java EE platform. As a result, most MVC (Model-View-Controller) Web frameworks integrate with Spring, including Spring's in-house module, Spring MVC. If you're developing Spring applications for the Web, familiarity with Spring MVC will help you step up your game while also leveraging all the familiar benefits of the Spring Framework.

To provide a context for understanding Spring MVC, I'll start with a brief history of Java Web applications, followed by an overview of the Spring Framework and its business value. I'll then introduce Spring MVC and walk you through a development project: a mini content-management system (CMS) that demonstrates how to build and deploy a Spring MVC application. You can download the source code any time.

Java Web applications: A brief history

This brief history begins with Java's entry into Web application development: the Servlet API, introduced in 1997. Before then, developing dynamic Web applications with disparate APIs and programming models proved to be challenging. (I'll resist the urge to recap the old days of CGI, the futile battle between Netscape Server plug-ins and ISAPI plug-ins, and all the homegrown APIs.) The Servlet API provided a standard on which vendors could build Web containers and developers could implement a simple programming model for constructing Web pages.

With servlets, dynamically constructing HTML documents became easy. But because the documents were constructed inside Java source code, maintenance was challenging. For example, changing a font or moving a piece of content within a page required you to update the HTML inside the servlet code and then recompile and redeploy it to the container.

Sun resolved this issue in 1999 by introducing JavaServer Pages (JSP). Rather than implementing presentation code inside servlets, JSPs lets you build HTML-like documents that interweave snippets of Java code to support dynamic content. At runtime, JSPs are translated to servlet source code, compiled, and then deployed to a Web container dynamically. If you change any part of the JSP, the container detects the change, recompiles the page, and redeploys it. This architecture was called page-driven or Model 1.

In page-driven applications, each page not only contains the business logic required to generate the dynamic content, but also the control logic to determine application flow. Control logic became difficult to maintain because there was no central location or design paradigm to help you understand how a user passed from one page to the next. And as business requirements evolved to include features like authentication and authorization, page code became convoluted with additional logic that usually had to be duplicated across pages, even if it was irrelevant to the page being constructed. On a site with 1,000 pages, all with authorization mechanisms, who wants to update all 1,000 pages when those authorization mechanisms change?

The solution was to separate programmatic responsibilities into the technologies best suited for them. Servlets are great for writing Java code, acting as a central point for managing application flow and invoking business methods, but horrible at presentation; JSPs are great at presentation but are a horrible place to embed all of your business logic. Thus was born the Model 2 architecture, which adheres to the MVC Smalltalk design pattern:

  • The model represents your data or the objects with which your application is interacting.
  • The view is a visualization of your model.
  • The controller manages application flow and makes calls into business objects.

In Java systems, the model is typically a loose JavaBean, or Plain Old Java Object (POJO) that has getters and setters to manipulate its contents. It can be a very simple object or a very complicated one with dozens of subobjects, but it's just an object nonetheless.

The view is usually a JSP page, but countless other technologies and templating languages can be used, such as FreeMarker, Velocity, XML/XSLT, JasperReports, and so on. After the business logic is complete and the model has been generated, the controller passes the model to the view to be presented to the user.

The controller is typically implemented by a servlet that calls into business services to perform some action. The controller usually hosts additional logic such as authentication and authorization: it controls which actions a user can execute. For example, you might not allow a user who hasn't first logged in to post a message to a message board. If the controller notices that the user is trying to access the "post" page, it redirects the user to a login page first.

In a Spring MVC architecture, you implement controllers that make calls into Spring service beans to perform business actions and then send a model object to one of your views for presentation. But I'm getting a bit ahead of myself. First we'll do a quick high-level review of Spring.

Spring: An overview

During the early days of Enterprise JavaBeans (EJB), developers led by Rod Johnson created the Spring Framework as a lightweight alternative to Java EE's complexity. Spring is many things -- entire books have been written about it -- but one of the key benefits that it pioneered is dependency injection (DI). DI is a design pattern, coined by Martin Fowler, that separates an application's dependencies (and dependency configuration) from the code that uses those dependencies. Spring implements DI by allowing you to "wire" together an application from beans, using either an XML configuration file or annotations. For example, if your application needs to access a database, you can configure your database connection pool in a Spring configuration file and tell Spring to "inject" a DataSource from that pool into your data-access object (DAO) bean; then you can inject that DAO into a service bean. The point is that your application should focus on solving its business problems rather than on the overhead required to construct resources and objects. Spring creates your objects for you and makes them available at runtime.

Another important point to understand about DI is how your objects are written. Consider a service bean that accesses a DAO object for data persistence:

public class MyService {
   private MyDao dao;
   public void setMyDao( MyDao dao ) {
      this.dao = dao;
   }
   public Widget businessMethod() {
      return dao.doBusinessThing();
   }
}

In traditional applications you would probably create a DAO factory object that, through a configuration file, creates the appropriate instance of the DAO on your behalf. In this example, Spring creates the correct MyDao instance (as you define it in the configuration file), creates the MyService object, and invokes the setMyDao() method to inject the DAO into the service. You write your application assuming that the DAO object is valid and is the correct implementation. Your service bean should not be concerned with creating the DAO object, but rather with invoking the correct method on the DAO object to accomplish the business objective.

The core business value in adopting Spring is the separation between code and configuration, which leads to a more manageable application.

Introducing Spring MVC

The aptly named Spring MVC is a full MVC implementation that follows the patterns and paradigms that Spring is known for, including DI. Spring provides a front controller servlet named DispatcherServlet. To build an application, you construct the following components:

  • One or more controllers that invoke business logic and create a ModelAndView object
  • A visualization component such as a JSP
  • XML or annotation configuration to wire the components together

Spring provides various controllers for you to use as base classes for creating your own controllers, depending on your needs. Among them are ones that:

  • Redirect to static views
  • Provide basic servlet-like functionality
  • Process commands
  • Process shared actions
  • Handle forms
  • Provide wizard-like functionality to process multipage forms

The rest of this article will be a hands-on introduction to Spring MVC, by way of a programming exercise. Together, we'll build a CMS that presents a list of articles, allows users to read articles, and allows users to post new articles. It's simple, but it demonstrates how to use a basic controller that accepts no parameters, a command controller that accepts the identifier of an article to display, and a form controller that processes an article submission.

Listing 1 shows the source code for the homepage controller. This simple controller accepts no request parameters and returns a ModelAndView object that contains a list of news articles.

Listing 1. HomePageController.java

package com.geekcap.geeknews.web;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import com.geekcap.geeknews.core.GeekNewsService;
import com.geekcap.geeknews.core.NewsArticle;

/**
 * The HomePageController is responsible for building the model of data to display
 * on the home page, which at this point contains a list of article overviews.
 * 
 * @author shaines
 *
 */
public class HomePageController extends AbstractController {
   /**
    * Provides access to GeekNews business methods
    */
   private GeekNewsService service;

   /**
    * Responsible for translating a web request into a ModelAndView object for presentation
    */
   @Override
   protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse res) throws Exception {
      
      // Use the service to load the articles
      List<NewsArticle> articles = service.getArticleOverviews();
      
      // Send the articles to the "home" view
      return new ModelAndView( "home", "articles", articles );
   }
   
   /**
    * Injected by Spring
    * @param service
    */
   public void setGeekNewsService( GeekNewsService service ) {
      this.service = service;
   }
}

The HomePageController extends Spring's AbstractController, which provides simple servlet-like functionality. It implements a single method -- handleRequestInternal() -- which accepts HttpServletRequest and HttpServletResponse objects, just as a servlet's service() method would. You could read parameters from the HttpServletRequest, but later I'll show you an easier alternative. The purpose of the AbstractController is to invoke business functionality and to generate a ModelAndView object from it.

In MVC vernacular, the model and view are separate entities, so you might wonder why a ModelAndView object seemingly pairs the two. In Spring MVC, the ModelAndView object is a container that hosts the model and provides insight to Spring's view resolver about how to locate the view. In the HomePageController, the ModelAndView is created with three parameters:

  • "home": The message that is sent to the view resolver to tell it to show the page identified by home (which I'll explain below).
  • "articles": An identifier for the model. When the view receives the model, it will be able to access it in the request through the "articles" key.
  • articles: The model itself.

The business logic, in this case, is delegated to the GeekNewsService, which Spring injects after it creates the HomePageController (shown below).

With the HomePageController built and a service bean that can be used to access back-end data, let's review the configuration of a Spring MVC application to observe how a request arrives at the Web container and then makes its way to the controller.

The primary entry point for a Spring application is the DispatcherServlet, so this first step is to create a DispatcherServlet in the web.xml file:

<servlet>
  <servlet-name>geeknews</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

Next, that DispatcherServlet needs to be mapped to a URI pattern. You can choose any pattern that you want, but most Spring applications map requests to pages ending in .htm. Add the following to your web.xml file:

<servlet-mapping>
  <servlet-name>geeknews</servlet-name>
  <url-pattern>*.htm</url-pattern>
</servlet-mapping>

In this case, all requests that end in .htm will be sent to the geeknews DispatcherServlet. By default, Spring looks for your Spring beans in a file whose name starts with the servlet name followed by -servlet.xml. So we need to create a geeknews-servlet.xml file that contains the HomePageController, our URL mapping strategy (to map URLs to controllers), and our view resolver, and place it in the WEB-INF directory. Listing 2 shows the contents of the geeknews-servlet.xml file.

Listing 2. geeknews-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"        
       xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-2.5.xsd">

   <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
     <property name="mappings">
          <props>
              <prop key="/home.htm">homePageController</prop>
           </props>
          </property>
    </bean>

   <bean id="homePageController" class="com.geekcap.geeknews.web.HomePageController">
      <property name="geekNewsService" ref="geekNewsService" />
   </bean>

   
   <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
          <value>/</value>
        </property>
        <property name="suffix">
          <value>.jsp</value>
        </property>
   </bean>
</beans>

The geeknews-servlet.xml file defines the HomePageController with the name homePageController. It injects the geekNewsService bean (as a reference, which means this is not a String value, but rather a reference to another bean) into its geekNewsService attribute. The geekNewsService bean is created in the geeknews-service.xml file (discussed below). In addition to defining the homePageController, the geeknews-servlet.xml file defines the urlMapping bean and the viewResolver bean.

The urlMapping bean is responsible for translating a URL pattern to a controller. There are several URL mappers:

  • BeanNameUrlHandlerMapping: Maps a URL to a bean based on the name of the controller's bean, as defined in the bean's XML definition.
  • SimpleUrlHandlerMapping: Maps a URL to a bean based on a list of properties. (In Listing 2, the SimpleUrlHandlerMapping class is used to map /home.htm to the bean with the ID of homePageController.)
  • ControllerClassNameHandlerMapping: Maps a URL to a bean based on the bean's class name. For example, HomePageController would be mapped to /homePage*, such as /home.htm.
  • ControllerBeanNameHandlerMapping: Similar to the BeanNameUrlHandlerMapping mapper, but does not expect bean names to follow the URL convention. Also supports Controller annotations.
  • CommonsPathMapHandlerMapping: Maps URLs to a controller based on Jakarta Commons Attributes metadata.
  • DefaultAnnotationHandlerMapping: Maps URLs to a controller for methods that implement the RequestMapping annotation.

The view resolver is responsible for translating the view name in the ModelAndView into a component, such as a JSP, that renders the view. The several available view resolvers are mostly based on the type of view they are forwarding to. View resolvers exist for FreeMarker, Jasper Reports, Velocity, XML, XSLT, and of course JSPs via URL paths. Listing 2 defines the InternalResourceViewResolver, which prefixes the view name with / and suffixes it with .jsp. Thus when the HomePageController returns the view name home, it is resolved to /home.jsp.

Putting it all together:

  1. The DispatcherServlet handles all requests that end in .htm, so it handles /home.htm.
  2. The SimpleUrlHandlerMapping maps /home.htm to the HomePageController.
  3. The HomePageController loads a list of articles from the GeekNewsService and returns those articles in a ModelAndView object whose destination is home.
  4. The InternalResourceViewResolver prefixes home with / and suffixes it with .jsp, which means that it is forwarded to /home.jsp for presentation.

Listing 3 shows the complete web.xml file.

Listing 3. web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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 web-app_2_4.xsd"
   version="2.4">

  <display-name>SpringMVC</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/geeknews-services.xml
        /WEB-INF/geeknews-dao.xml
    </param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>geeknews</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>geeknews</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>

  <taglib>
    <taglib-uri>http://java.sun.com/jstl/core</taglib-uri>
    <taglib-location>/WEB-INF/tld/c.tld</taglib-location>
  </taglib>

  <taglib>
    <taglib-uri>http://java.sun.com/jstl/fmt</taglib-uri>
    <taglib-location>/WEB-INF/tld/fmt.tld</taglib-location>
  </taglib>

  <welcome-file-list>
   <welcome-file>index.html</welcome-file>
  </welcome-file-list>

</web-app>

The DispatcherServlet automatically loads the geeknews-servlet.xml file, but when you build Spring applications it's a good practice to divide bean XML files into their logical components. In Listing 3, the contextConfigLocation context parameter defines two additional Spring XML files that should be loaded: geeknews-services.xml and geeknews-dao.xml. The ContextLoaderListener class is responsible for loading resources and reads the contextConfigLocation parameter to determine which configuration files to load. Finally, the web.xml file imports two components of the Java Standard Tag Library (JSTL) that will be used in the JSP file.

Listing 4 shows the source code for the home.jsp file.

Listing 4. home.jsp

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.geekcap.geeknews.core.*,java.util.List"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      <title>Geek News</title>
      <link type="text/css" rel="stylesheet" href="css/geeknews.css" />
   </head>

<body>

<%@ include file="header.jsp" %>

<div id="mainContent">

<p><a href="post.htm">Post</a></p>

<c:forEach items="${articles}" var="article">

<div class="article">
<p><a href="article.htm?id=<c:out value="${article.id}"/>"><c:out value="${article.title}" /></a> 
   by <span class="articleAuthor"><c:out value="${article.author}" /></span>
   on <span class="articleDate"><fmt:formatDate value="${article.date}" type="both" /></span>
</p>

<p class="articleSummary"><c:out value="${article.summary}" /></p>
</div>

</c:forEach>

</div>


<%@ include file="footer.jsp" %>

</body>
</html>

The details of the HTML and CSS presentation are unimportant for this discussion; what's important is that the request object has an articles variable in it. The following snippet demonstrates how the JSTL core library is used to iterate over all articles and display an article's author, title, publication date, and summary:

<c:forEach items="${articles}" var="article">

<p><a href="article.htm?id=<c:out value="${article.id}"/>">
       <c:out value="${article.title}" /></a> 

   by <c:out value="${article.author}" />
   on <fmt:formatDate value="${article.date}" type="both" />
</p>

<p class="articleSummary"><c:out value="${article.summary}" /></p>

</c:forEach>

Figure 1 shows a screenshot of the Geek News homepage.

Geek News home page
Figure 1. Geek News home page (Click to enlarge.)

The header consists of the words GEEK NEWS, hyperlinked to the home page. The body consists of a Post link, which brings up a form (which you'll implement later) for posting new articles, and then one section for each article that includes the title, author, publication date, and summary, followed by a horizontal line (implemented as a CSS bottom border on the article <div>). A footer (not shown) has copyright information (for "Fictitious Company," so don't worry, the code is yours to keep).

The header and footer are imported in the home.jsp page through the JSP include directive. They are both JSPs themselves. In their current state, they could be simple HTML documents, but in the future you might want to add a login link to the header or administration links to the footer.

Setup and deployment

Before you can compile and deploy the HomePageController, you need to download, decompress, and install the latest Spring Framework version (as of this writing, version 2.5.6) and its dependencies.

Spring partitions its functionality into several JAR files, so your WAR file can include only the functionality you need. It's up to you to track down all of the JAR files that you do need. The sample application needs the following Spring resources in its WEB-INF/lib folder, which you can find in Spring's dist/modules directory:

  • spring-beans.jar
  • spring-context.jar
  • spring-core.jar
  • spring-web.jar
  • spring-webmvc.jar

It also needs these resources, located in Spring's lib subdirectories:

  • commons-codec.jar (lib/jakarta-commons)
  • commons-logging.jar (lib/jakarta-commons)
  • jstl.jar (lib/j2ee)
  • standard.jar (lib/jakarta-taglibs)
  • log4j-1.2.15.jar (lib/log4j)

Finally, the Dao class uses JDOM, so you need to download JDOM. Then add jdom.jar (in JDOM's build directory) and xerces.jar (in JDOM's lib directory) to your WAR file.

With these JAR files in your CLASSPATH, you can compile the sample application with the Ant build.xml file included in the source-code download. This file loads a build.properties file that defines the following properties (which you must update to match your environment):

  • tomcat.home: The location where you have Tomcat installed; if you want to use a different application server then you may need to update the build.xml file to include the JAR file that contains the Servlet API..
  • jdom.home: The location where you have JDOM 1.1 installed
  • spring.home: The location where you have Spring installed.

In the end your WAR file should contain the following files:

index.html
article.jsp
footer.jsp
header.jsp
home.jsp
post.jsp
postSuccess.jsp
css/geeknews.css
images/articlebackground.jpg
images/blockquotebackground.gif
images/codebackground.gif
WEB-INF/web.xml
WEB-INF/geeknews-dao.xml
WEB-INF/geeknews-services.xml
WEB-INF/geeknews-servlet.xml
WEB-INF/classes/log4j.properties
WEB-INF/classes/com/geekcap/geeknews/core/GeekNewsService.class
WEB-INF/classes/com/geekcap/geeknews/core/NewsArticle.class
WEB-INF/classes/com/geekcap/geeknews/dao/FileSystemNewsArticleDaoImpl.class
WEB-INF/classes/com/geekcap/geeknews/dao/NewsArticleDao.class
WEB-INF/classes/com/geekcap/geeknews/web/HomePageController.class
WEB-INF/classes/com/geekcap/geeknews/web/LoadArticleController.class
WEB-INF/classes/com/geekcap/geeknews/web/PostArticleFormController.class
WEB-INF/classes/com/geekcap/geeknews/web/command/ArticleCommand.class
WEB-INF/classes/com/geekcap/geeknews/web/validator/NewsArticleValidator.class
WEB-INF/lib/commons-codec.jar
WEB-INF/lib/commons-logging.jar
WEB-INF/lib/jdom.jar
WEB-INF/lib/jstl.jar
WEB-INF/lib/log4j-1.2.15.jar
WEB-INF/lib/spring-beans.jar
WEB-INF/lib/spring-context.jar
WEB-INF/lib/spring-core.jar
WEB-INF/lib/spring-web.jar
WEB-INF/lib/spring-webmvc.jar
WEB-INF/lib/standard.jar
WEB-INF/lib/xerces.jar
WEB-INF/tld/c.tld
WEB-INF/tld/fmt.tld
WEB-INF/tld/fn.tld
WEB-INF/tld/permittedTaglibs.tld
WEB-INF/tld/scriptfree.tld
WEB-INF/tld/sql.tld
WEB-INF/tld/x.tld

If you're running Tomcat, you can deploy the application by invoking the Ant deploy target in the project build script. The deploy target copies the WAR file to Tomcat's webapps directory.

Start Tomcat, open your Web browser, and open http://localhost:8080/GeekNews/home.htm. Or you can exclude home.htm from the URL to invoke the index.html file, which automatically redirects you to the home page.

To make the example clearer, I decided to store articles in XML files in the directory of your choice, rather than in a database. To make it work properly, you must create an article directory and update the articleDirectory property defined on the newsArticleDao bean in the geeknews-dao.xml file. When you first launch the application, it won't have any content. So, after you build the source code for the entire project, click on Post and complete the form (the date needs to be of the format: MM/DD/YYYY HH:MM AM or PM, for example: "03/10/2009 09:00 PM"). The content will be dropped right onto the Web page, so you are free to use HTML markup such as <p>, <blockquote>, <pre>, <code>, and so forth. I don't recommend adding extra <html> or <div> tags, but the basics work.

Adding more controllers

The HomePageController that you've finished building is essential for the mini-CMS application, but it's only a start. The full application also needs a controller that responds to request parameters, and one for submitting content. Next you'll build the LoadArticleController, which responds to a single request parameter; after that, you'll implement the form controller for article submissions.

Command controllers

The LoadArticleController accepts a single request parameter -- id -- and uses the GeekNewsService to load the contents of the article that has that identifier. Looking back at the HomePageController, you could simply obtain the id parameter from the request object as you would do in a servlet:

String id = ( String )request.getAttribute( "id" );

But Spring provides a more elegant controller that obtains that parameter (and any others you need) from the request, in the form of a command object. Listing 5 shows a command object that wraps the id attribute.

Listing 5. ArticleCommand.java

package com.geekcap.geeknews.web.command;

public class ArticleCommand {

    private String id;

    public ArticleCommand() {
    }

    public ArticleCommand(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

ArticleCommand is a simple POJO with a single parameter: id. When the POJO is paired with the appropriate controller type, Spring examines the request and tries to populate each field in the POJO. For a single parameter this might seem like overkill, but if you had 10 parameters of varying types, you would otherwise need to extract them manually as Strings and convert them to the appropriate types. You might as well take advantage of Spring's capability to do this for you.

Listing 6 shows the source code for the LoadArticleController.

Listing 6. LoadArticleController.java

package com.geekcap.geeknews.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import com.geekcap.geeknews.core.GeekNewsService;
import com.geekcap.geeknews.core.NewsArticle;
import com.geekcap.geeknews.web.command.ArticleCommand;

public class LoadArticleController extends AbstractCommandController {

    /**
     * Provides access to GeekNews business methods
     */
    private GeekNewsService service;

    public LoadArticleController() {
        setCommandName( "article" );
        setCommandClass( ArticleCommand.class );
    }

    @Override
    protected ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object command, BindException errors ) throws Exception {

        // Load our article command
        ArticleCommand articleCommand = ( ArticleCommand )command;
    
        // Load the article from the GeekNewsService
        NewsArticle article = service.getArticle( articleCommand.getId() );
        
        // Build and return our ModelAndView
        return new ModelAndView( "article", "article", article );
    }

    /**
     * Injected by Spring
     * @param service
     */
    public void setGeekNewsService( GeekNewsService service ) {
        this.service = service;
    }
}

LoadArticleController extends Spring's AbstractCommandController. In two steps, you configure AbstractCommandController to auto-populate the command POJO of your choice:

  1. In the constructor, call setCommandClass() and pass it the command's Class object. The additional call to setCommandName() makes the command object available to the view, in the model, under the specified name.
  2. Implement the handle() method. The handle() method includes a command object that can be cast to your command class.

Now, the GeekNewsService can be invoked to find the article with the specified identifier by calling the command's getId() method.

The LoadArticleController is defined in the Spring XML configuration file in much the same way as the homePageController: define the bean with the injected GeekNewsService and add it to the urlMapping:

<bean id="loadArticleController" class="com.geekcap.geeknews.web.LoadArticleController">
    <property name="geekNewsService" ref="geekNewsService" />
</bean>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
                <prop key="/home.htm">homePageController</prop>
                <prop key="/article.htm">loadArticleController</prop>
            </props>
    </property>
</bean>

The LoadArticleController returns a NewsArticle object in the model, which is redirected to the "article" page. Recall that the view resolver prefixes article with / and suffixes it with .jsp, so the view page can be found at /article.jsp. The article.jsp page -- shown in Listing 7 -- is similar to the home.jsp page, except that it shows only one article and displays its content rather than its summary.

Listing 7. article.jsp

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.geekcap.geeknews.core.*,java.util.List"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Article: <c:out value="${article.title}" /></title>
    <link type="text/css" rel="stylesheet" href="css/geeknews.css" />
</head>

<body>

<%@ include file="header.jsp" %>

<div id="mainContent">

<p><a href="home.htm">Home</a></p>

<div id="articleContentHeading">

<h2><c:out value="${article.title}" /></h2>
<p>By <span class="articleAuthor"><c:out value="${article.author}" /></span>
   on <span class="articleDate"><fmt:formatDate value="${article.date}" type="both" /></span>
</p>

</div>

<div id="articleContent">
<c:out value="${article.content}" escapeXml="false" />
</div>

</div>

<%@ include file="footer.jsp" %>

</body>
</html>

Figure 2 shows a screen shot of the article page.

Geek News article
Figure 2. Reading a Geek News article

Form controllers

Now you'll build the PostArticleFormController, which handles a form posting, complete with simple form validation. Figure 3 shows a screen shot of the Geek News article-submission page.

Geek News submission page
Figure 3. Posting a Geek News article

The submission page prompts the user for an article title, author, submission date, summary, and content (with HTML markup). It is backed by a NewsArticle object, which is a POJO that wraps these parameters, shown in Listing 8.

Listing 8. NewsArticle.java

package com.geekcap.geeknews.core;

import java.util.Date;

public class NewsArticle implements Comparable {

    private String id;
    private String title;
    private String author;
    private String summary;
    private String content;
    private Date date;

    public NewsArticle() {
        
    }

    public NewsArticle(String title, String author, Date date, String summary, String content ) {

        this.title = title;
        this.author = author;
        this.summary = summary;
        this.content = content;
        this.date = date;
    }

    public NewsArticle(String id, String title, String author, Date date, String summary, String content) {

        this.id = id;
        this.title = title;
        this.author = author;
        this.date = date;
        this.summary = summary;
        this.content = content;
    }

    public String getId() {
        return id;
    }
    
    public void setId(String id) {
        this.id = id;
    }
    
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    
    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
    
    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getContent() {
        return content;
    }
    
    public void setContent(String content) {
        this.content = content;
    }

    /**
     * Sorts article by descending dates
     */
    public int compareTo(Object o) {
        
        // Comparing the other date to ours should order descending 
        int result = ( ( NewsArticle )o ).getDate().compareTo( date );
        if( result == 0 ) {
            // Ensure that we don't lose any articles that have the same date
            return 1;
        }
        return result;
    }
}

The NewsArticle class is a straightforward POJO. The only thing unusual about it is that it is Comparable and implements the compareTo() method. This enables articles to be sorted by their date, from most recent to least recent.

Listing 9 shows the source code for the PostArticleFormController.

Listing 9. PostArticleFormController.java

package com.geekcap.geeknews.web;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

import com.geekcap.geeknews.core.GeekNewsService;
import com.geekcap.geeknews.core.NewsArticle;

public class PostArticleFormController extends SimpleFormController {

    /**
     * Provides access to GeekNews business methods
     */
    private GeekNewsService service;

    public PostArticleFormController() {
        setCommandClass( NewsArticle.class );
        setCommandName( "newsArticle" );
    }
    
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
        SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm aa");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(format, true));
    }
    
    protected ModelAndView onSubmit(Object command, BindException bindException) throws Exception {
        
        NewsArticle article = ( NewsArticle )command;
        
        service.addArticle( article );
        
        return new ModelAndView( getSuccessView() );
    }
    
    /**
     * Injected by Spring
     * @param service
     */
    public void setGeekNewsService( GeekNewsService service ) {
        this.service = service;
    }
}

The PostArticleFormController extends Spring's SimpleFormController and registers the NewsArticle as its command class, which in turn will be the container for its form data. The SimpleFormController class behaves differently depending on whether the Web request sent to it is a GET or a POST. If the request is a GET, it redirects the request to the form to be completed. If the request is a POST, the form object (NewsArticle in this example) is optionally validated and then passed as the command object to the onSubmit() method. In the PostArticleFormController, the onSubmit() method, knowing that its article is valid, simply invokes the GeekNewsService's addArticle() method to add the article to the CMS repository and then returns a ModelAndView object that references the getSuccessView() view.

The SimpleFormController defines two views: the form view and the success view. Users who have not successfully completed the form are redirected to the form view, otherwise, on successful submission, they are redirected to the success view. The form and success views are defined in the XML bean definition:

<bean id="postArticleFormController" class="com.geekcap.geeknews.web.PostArticleFormController">
    <property name="formView" value="post" />
    <property name="successView" value="postSuccess" />
    <property name="geekNewsService" ref="geekNewsService" />
    <property name="validator">
        <bean class="com.geekcap.geeknews.web.validator.NewsArticleValidator" />
    </property>
</bean>

Understanding how the view resolver works, you can probably already determine that the form view resolves to /post.jsp and the success view resolves to /postSuccess.jsp. The GeekNewsService is injected as usual, but there is a new property: validator. The validator defines a class that's responsible for validating the command object (NewsArticle in this example.) Listing 10 shows NewsArticleValidator's source code.

Listing 10. NewsArticleValidator.java

package com.geekcap.geeknews.web.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import com.geekcap.geeknews.core.NewsArticle;

public class NewsArticleValidator implements Validator {

    @Override
    public boolean supports( Class clazz ) {
        return clazz.equals( NewsArticle.class );
    }

    @Override
    public void validate( Object command, Errors errors ) {

        NewsArticle article = ( NewsArticle )command;
    
        // Validate required fields
        ValidationUtils.rejectIfEmptyOrWhitespace( errors, "title", "article.missingTitle", "A title must be specified" );
        ValidationUtils.rejectIfEmptyOrWhitespace( errors, "author", "article.missingAuthor", "An author must be specified" );
        ValidationUtils.rejectIfEmptyOrWhitespace( errors, "summary", "article.missingSummary", "A summary must be specified" );
        ValidationUtils.rejectIfEmptyOrWhitespace( errors, "content", "article.missingContent", "Content must be specified" );
    
    }
}

Validators must report the classes that they validate through the supports() method. The NewsArticleValidator compares the class passed to it with the NewsArticle class. If the classes are the same, then it returns true, meaning that Spring can safely invoke validate() on this validator, passing it a NewsArticle.

The NewsArticleValidator performs only simple validation checking to see if the title, author, summary, and contents have values in them. You might want to extend this validation to look for invalid characters in the title, validate that the author currently exists in the system, validate that the date is valid and after "now," and so forth. This is your entry point to do so. The validator makes use of Spring's ValidationUtils class, which provides a handful of helper methods to check whether or not fields are populated. Spring also supports the Jakarta Commons Validator library's more-robust validation capabilities. The important thing to note about this example is that the errors object is updated with an error condition if a problem occurs. This tells the SimpleFormController to redirect the user back to the form, with error messages.

Internationalization support

The third parameter passed to the ValidationUtils methods is a key into a properties file from which Spring can look up the value to display as the error message. This is for internationalization, which is beyond the scope of this article. Just know that Spring supports internationalization, and this is one example of where it does so.

Listing 11 shows the source code for the post.jsp file.

Listing 11. post.jsp

<?xml version="1.0" encoding="UTF-8" ?>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.geekcap.geeknews.core.*,java.util.List"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Post a New Article</title>
    <link type="text/css" rel="stylesheet" href="css/geeknews.css" />
</head>

<body>

<%@ include file="header.jsp" %>

<div id="articleForm">

<p><a href="home.htm">Home</a></p>

<p id="articleFormInstructions">Enter the information for an article posting and press "Post"</p>

<form:form action="post.htm" method="POST" commandName="newsArticle" >

<table>
    <tr>
        <th rowspan="2">Title:</th>
        <td><form:input path="title" size="60" /></td>
    </tr>
    <tr>
        <td><form:errors path="title" cssClass="validationError"/></td>
    </tr>
    <tr>
        <th rowspan="2">Author:</th>
        <td><form:input path="author" size="60" /></td>
    </tr>
    <tr>
        <td><form:errors path="author" cssClass="validationError"/></td>
    </tr>
    <tr>
        <th rowspan="2">Date:</th>
        <td><form:input path="date" size="60" /></td>
    </tr>
    <tr>
        <td><form:errors path="date" cssClass="validationError"/></td>
    </tr>
    <tr>
        <th rowspan="2">Summary:</th>
        <td><form:textarea path="summary" rows="3" cols="90"></form:textarea></td>
    </tr>
    <tr>
        <td><form:errors path="summary" cssClass="validationError"/></td>
    </tr>
    <tr>
        <th rowspan="2">Content:</th>
        <td><form:textarea path="content" rows="20" cols="90"></form:textarea></td>
    </tr>
    <tr>
        <td><form:errors path="content" cssClass="validationError"/></td>
    </tr>
    <tr>
        <th></th>
        <td><input type="submit" value="Post" /></td>
    </tr>
</table>

</form:form>

</div>

<%@ include file="footer.jsp" %>

</body>

</html>

The important observation to make about post.jsp is that it uses Spring form tags as opposed to HTML form tags. It begins by importing the Spring form tag library:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

Then it's possible to preface normal form tags with form:. For example, the <form> tag is substituted with <form:form>. These tags also include some additional parameters:

  • The <form:form> tag's additional commandNameparameter should match up to your SimpleFormController's setCommandName() parameter.
  • The <form:input> and <form:textarea> tags have a path parameter instead of a name parameter. The path parameter is used during form validation to repopulate the value of a tag if the form needs to be redisplayed. If you look at the generated HTML, you can see that it is translated to name.

There's also a new element: <form:error>. If an error occurs during form validation, the error tag for the specified path is populated with the error message defined by the form validator. In the post.jsp page, the errors are presented in new table rows that follow the field, but you are free to put them anywhere, such as at the top of the form. Use the strategy that fits best into your user interface design.

In conclusion

In a few easy lessons, you've built a fully functioning Spring MVC application from scratch. You started by using a simple controller to construct a model and pass it to a JSP file for presentation. Then you added a command controller and a form controller for form validation.

Spring MVC is a highly capable framework and a powerful extension to the Spring Framework. It provides several ways to map request URIs to controllers, defines several controller base classes to suit your business needs, and gives you multiple options for mapping view names to views. If you are building Spring applications, Spring MVC certainly should be at the top of your short list of Web frameworks.

Steven Haines is the founder and CEO of GeekCap, Inc., which provides technical e-learning solutions for software developers. Previously he was the Java EE Domain Expert at Quest Software, defining software used to monitor the performance of various Java EE application servers. He is the author of Pro Java EE 5 Performance Management and Optimization, Java 2 Primer Plus, and Java 2 From Scratch. He is the Java host on InformIT.com and a Java Community Editor on InfoQ.com. Steven has taught Java at the University of California, Irvine, and Learning Tree University.

Learn more about this topic

More from JavaWorld

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more