Mastering Spring MVC

Enjoy Spring-based Web development with the Spring MVC module

1 2 3 4 Page 3
Page 3 of 4

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:

1 2 3 4 Page 3
Page 3 of 4