2010-01-30

REST with Spring 3.0, Spring MVC and Dojo. Part 1 - GET

Introduction


I am going to write a few posts on RESTful application development with Spring 3.0, using Spring MVC, Dojo and Maven. In the first part, I will show you how to create a basic controller and handle a GET request. The rest: DELETE, POST, PUT we will cover later.

I would suggest that - in case you're not very familiar with REST - you read some information about REST, for instance A Brief Introduction to REST from InfoQ or this article on REST conventions.

I am going to use a Maven project in Eclipse Ganymede with the M2Eclipse plugin, the latest development build (0.9.9.200912160759), as the latest production release contains some weird bugs including this annoying bug that seems to be fixed in the development build (it's time to release, guys!).

I am also going to use JBoss 5.1.0 JDK6 and that's why I will put one of the Spring beans files (applicationContext.xml) in the classes folder of the output war which is ugly but it works (it didn't work when I put it in /WEB-INF/; it did work in WebSphere though). Funny that when I called it beans.xml I would actually get an exception from JBoss: javax.inject.DefinitionException: bean not a Java type. Handy, I guess we're getting somewhere with error reporting.

One more thing, I am using Java 1.6 but Java 1.5 will suffice for everything - except for validation where a Java 6 annotation will be used. It is possible to implement your own validation though, it's really up to you. Validation will be shown in the POST and PUT parts. In this post only very basic (type) validation will be shown.

What you will learn


Let us begin! We are going to handle GET requests - in this example two GET requests. One without parameters (it will return all books - books will be sample items in this example) and one with a parameter - book ID. This will allow you to see:

  • How to handle input RESTful parameters with Spring MVC
  • How to return a collection of items
  • How to return a single item

Operation REST method Sample URI
Return item(s) GET /books (all books)
/books/ (all books)
/books/12 (one book with ID = 12)
Create a new item POST (you pass the ID in the object itself)
/books
/books/
Update an item PUT (you pass the ID in the object itself)
/books
/books/
Delete an item DELETE /books/12 (book with ID = 12)

The controller


Let's take a look at our controller class - the controller will handle the requests.

package me.m1key.restsample.controllers;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
// Some imports excluded for brevity, eh?

/**
 * Books controller.
 *
 * @author Michal Huniewicz
 *
 */
@Controller
@RequestMapping("/books")
public class SampleController {
    // Methods excluded for brevity.
}

This is it, it doesn't have to implement or extend anything. It is only annotated as a controller with the @Controller annotation. It means it can have many actions, like MultiActionController in the old days.

It is also annotated with @RequestMapping. This is where we specify the path which this controlller is to handle. As you will see, the action methods will also be annotated with this to further narrow down the criteria.

Setup


Now, how will Spring know about this Controller? We will tell it specifying certain information, first in the web.xml file.

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Books REST Handler</display-name>

    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>rest.root</param-value>
    </context-param>

    <!-- Servlets -->

    <servlet>
        <servlet-name>rest</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>rest</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
    
    <!-- Rest of the file omitted for brevity. -->

</web-app>

In the web.xml file we declare the dispatcher servlet from Spring and we ask the server to load it on startup. We also assign this servlet to a certain path, that is /rest/* preceded by application's context root.

Now, because we specified the servlet name to be rest, we will create one more file next to web.xml (in the /WEB-INF/ folder that is) called rest-servlet.xml. While web.xml is a standard JEE file, this rest-servlet.xml file is a Spring Framework kind of file.

<?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"
    xmlns:oxm="http://www.springframework.org/schema/oxm"
    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
                http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

    <context:component-scan base-package="me.m1key.restsample.controllers" />
    
    <!-- Rest of the file omitted for brevity. -->



And this is how we tell Spring to search for components: with the context:component-scan element that specifies where to look for components in our application. An alternative would be to declare all the beans separately with the bean element.

Controller actions - return all books


What we did so far is we declared a controller, we told the server and Spring about it. It's time to write some actions.

package me.m1key.restsample.controllers;

import java.util.List;

import me.m1key.restsample.Factory;
import me.m1key.restsample.beans.BooksBean;
import me.m1key.restsample.to.BookTO;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * Books controller.
 *
 * @author Michal Huniewicz
 *
 */
@Controller
@RequestMapping("/books")
public class SampleController {

    private static final String BOOKS = "books";

    /**
     * Returns all books.
     *
     * @return all books
     */
    @RequestMapping(value = "", method = RequestMethod.GET)
    public ModelAndView handleAllBooks() {
        ModelAndView mav = new ModelAndView();

        BooksBean booksBean = Factory.getBooksBean();

        List booksTO = booksBean.loadBooks();

        mav.addObject(BOOKS, booksTO);

        return mav;
    }

    // Rest of the code omitted for brevity.

}

Please take a look at the handleAllBooks method. It is annotated with @RequestMapping, like promised. That means that it will handle /books/ (or /books without the trailing backslash slash backslash slash) requests (within the app context root). What it returns is a ModelAndView object, that's it. We use BooksBean to get us the list of all books as controllers shouldn't really contain that kind of logic (see the MVC pattern). In our example BooksBean is just a dummy object, so it doesn't use any data source like it would in a real application.

There are two more things I must mention.

  • You should not call ModelAndView#addObject() more than once because if you do you might get inconsistent results depending on the output format (JSON/XML/...). To be precise, with JSON you would get all the objects no matter how many times you call addObject. With XML - just one. It looks like a bug but Arjen Poutsma was kind enough to explain to me that it isn't.
  • Wait a second, I'm talking JSON, XML, but the code just returns a ModelAndView object... That's right, the Controller is not aware of the output method. Ideally, we should be able to say /rest/books.json and get JSON response, /rest/books.xml and get XML response, /rest/books.html and get HTML response, /rest/books.mp3 and get a bunch of ladies singing the book titles. In this simple application we are going to handle only JSON and XML.

Telling Spring about the output methods we support - XML and JSON


<?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"
    xmlns:oxm="http://www.springframework.org/schema/oxm"
    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
                http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

    <context:component-scan base-package="me.m1key.restsample.controllers" />

    <bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean id="mappingJacksonHttpMessageConverter"
                    class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
            </list>
        </property>
    </bean>
    <bean
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
                <entry key="xml" value="application/xml" />
            </map>
        </property>
        <property name="defaultViews">
            <list>
                <bean
                    class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
                <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
                    <constructor-arg>
                        <bean class="org.springframework.oxm.xstream.XStreamMarshaller"
                            p:autodetectAnnotations="false" />
                    </constructor-arg>
                </bean>

            </list>
        </property>
        <property name="defaultContentType" ref="jsonMediaType" />
        <property name="ignoreAcceptHeader" value="false" />
    </bean>

    <bean id="jsonMediaType" class="org.springframework.http.MediaType">
        <constructor-arg value="application/json" />
    </bean>

</beans>

Update 2010-03-31: Ralph Engelmann reported that the last bean definition is not valid as of Spring 3.0.1.RELEASE (see comments below). If you are using this version of later, you must define this bean in the following manner:
<bean id="jsonMediaType" class="org.springframework.http.MediaType">
    <constructor-arg value="application"/>
    <constructor-arg value="json"/>
</bean>
Thanks goes to Ralph.

Whew!

The first part (with AnnotationMethodHandlerAdapter) tells Spring which input methods we support (it will be useful when we send objects to the server via REST).

The second part (with ContentNegotiatingViewResolver) tells Spring which output methods we support (in this example, JSON and XML, as you can see).

Below we also define the default (preferred by us) output format. Please note that the client can still ignore it and request another one i