Spring 3.0 RESTful Web Services By Example

Update:See this post for an updated code example that works with Spring 3.0.1.RELEASE.

If you’re reading this then you’re probably aware that the new Spring 3.0 release will have REST support (If you’re not familiar with REST here is a nice intro).  In this article I’m going to describe the basic steps required to quickly get a RESTful XML web service going using the latest Spring 3 release candidate (3.0.0.RC3).  In future follow-up articles I will describe how to switch between JSON and XML using selectors and how to use the Spring REST Template to read RESTful web services.

Before I get started I’m going to need to make some assumptions about you, the reader.  This is not a beginner Spring, MVC, or Java EE tutorial, so in order to keep the length of this article reasonable I need to assume certain knowledge.  First, I’m hoping that you are familiar with basic Spring 2.5.3+ IoC fundamentals.  It would also be very helpful to understand Spring MVC fundamentals as well.  You’ll also need to be familiar with the structure and configuration of a Java EE web project.  Finally, if you plan to download and use the example code, it’s necessary to know enough about Maven to be able to build a project.

Getting Spring 3.0.0.RC3

Obviously before you do anything you need to get the Spring 3 binaries.  I recommend using Maven to handle the Spring JARs and their dependencies.  This article will assume that you are using Maven. However, if you are a Maven holdout then you can download the binaries and put them in your classpath manually if you wish. Since 3.0.0 has not been released yet, the only way to give Maven access to it is via Spring’s bundle repositories. I find that the easiest way to do this is to include the repository entries in my settings.xml file, but you can also put them in the POM file for your project or even add them to Nexus as proxy repositories if you are in such an environment. The entries should look like this:

<repositories> <repository> <id>SpringSource Enterprise Bundle Repository – External Bundle Milestones</id> <url>http://repository.springsource.com/maven/bundles/milestone</url> </repository> <repository> <id>SpringSource Enterprise Bundle Repository – SpringSource Bundle Releases</id> <url>http://repository.springsource.com/maven/bundles/release</url> </repository> <repository> <id>SpringSource Enterprise Bundle Repository – External Bundle Releases</id> <url>http://repository.springsource.com/maven/bundles/external</url> </repository> </repositories>

With these entries in place Maven should now be able to import Spring 3.0 binaries and certain other useful bundles.

Configuring the POM file

I’m using the following dependencies. If you’re using Maven you can probably get away with not declaring all of them. If you’re not using Maven you may find that there are some that you still need. That’s because Maven does transitive dependency management for you. The thing to note here is that, starting with Spring 3.0, there is no “everything jar”, so you have to figure out what JARs you’ll need for a particular functionality.

<dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.web.servlet</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>com.springsource.javax.servlet</artifactId> <version>${servlet-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.stupidjavatricks.spring</groupId> <artifactId>spring-rest-example-model</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>com.google.collections</groupId> <artifactId>google-collections</artifactId> <version>1.0-rc4</version> </dependency>

The Book Search Service

I’ve tried to keep the example as simple as possible without being too trivial. I chose a book search as it seemed fairly simple. The idea is to use a search service to return a POJO or a list of POJOs of some kind (e.g. Book.java) in response to a query. In this example I’m searching for books or authors. If the class structure doesn’t seem that well thought out, that’s because it’s not; it’s just an example. It also might not meet your definition of “RESTful.” If that’s the case then I apologize in advance for ruining your day. 😉

Book.java

@XStreamAlias(“book”) public class Book {   private String isbn; private String title; private int edition; private int pages; private String published; private List authors; private Publisher publisher; … }

Author.java

@XStreamAlias(“author”) public class Author {   private String authorId; private String name; private String email; … }

Publisher.java

@XStreamAlias(“publisher”) public class Publisher {   private String name; private Address address; … }

Address.java

@XStreamAlias(“address”) public class Address {   private String address; private String city; private String state; private String zip; … }

You’ll notice that there’s not much interesting about any of these classes save for the annotations. The @XStreamAlias annotation is used by XStream to tell it what to name the XML element representing a class. The default is to use a fully-qualified class name (e.g. <com.stupidjavatricks.books.Book>), which can get verbose and also exposes the underlying class. If you don’t mind about either of those things then you don’t need to worry about using the annotations.

My ultra-simple search interface looks like this:

BookService.java

public interface BookService { public Book getBookByIsbn(String isbn); public List getBooksByAuthor(String authorId); public List getAllBooks(); public List getAllAuthors(); public Author getAuthorById(String authorId); }

My implementation is not important, but for simplicity and portability I’ve chosen mock data. You can implement your own to use a database or whatever you like.

The Controller

The controller for this example is just a plain old Spring MVC annotated controller with one difference: @PathVariable. This annotation binds URL template variables to method parameters. It is described in more detail in an article by Arjen Poutsma of the Spring team.

BookServiceController.java

@Controller public class BookServiceController {   @Autowired BookService bookService;   @RequestMapping(value = “/books/”) public ModelAndView getAllBooks() { List books = bookService.getAllBooks(); ModelAndView mav = new ModelAndView(“bookXmlView”, BindingResult.MODEL_KEY_PREFIX + “books”, books); return mav; }   @RequestMapping(value = “/books/{isbn}”) public ModelAndView getBookByIsbn(@PathVariable String isbn) { Book book = bookService.getBookByIsbn(isbn); ModelAndView mav = new ModelAndView(“bookXmlView”, BindingResult.MODEL_KEY_PREFIX + “book”, book); return mav; }   @RequestMapping(value = “/authors/”) public ModelAndView getAllAuthors() { List authors = bookService.getAllAuthors(); ModelAndView mav = new ModelAndView(“bookXmlView”, BindingResult.MODEL_KEY_PREFIX + “authors”, authors); return mav; }   @RequestMapping(value = “/authors/{authorId}”) public ModelAndView getAuthorById(@PathVariable String authorId) { Author author = bookService.getAuthorById(authorId); ModelAndView mav = new ModelAndView(“bookXmlView”, BindingResult.MODEL_KEY_PREFIX + “author”, author); return mav; }   @RequestMapping(value = “/authors/{authorId}/books”) public ModelAndView getBooksByAuthor(@PathVariable String authorId) { List books = bookService.getBooksByAuthor(authorId); ModelAndView mav = new ModelAndView(“bookXmlView”, BindingResult.MODEL_KEY_PREFIX + “books”, books); return mav; } }

Once again: nothing out of the ordinary. I’m simply using the @RequestMapping annotation to bind URL templates to methods and then using an injected BookService to do the work. I’m also returning a model and view. Something to note is that I’m prefixing the name of the model object with the constant BindingResult.MODEL_KEY_PREFIX. This is necessary because of what seems to be a bug in the MarshallingView.java class. Then again, maybe it’s undocumented behavior. Either way – including this prefix will save you headaches.

Wiring it up

Now that I’ve got my model classes created and my controller created and annotated, I’ll need to do a little configuration. The first thing I want to configure is the web.xml file. I need to add a Servlet and Servlet mapping.

WEB-INF/web.xml (excerpt)

<servlet> <servlet-name>books</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>   <servlet-mapping> <servlet-name>books</servlet-name> <url-pattern>/booksearch/*</url-pattern> </servlet-mapping>

Here I’ve created a Spring DispatcherServlet named books and mapped it to the /booksearch/* URL pattern. Nothing new here. Now I need to configure the Spring context XML file for this Servlet.

WEB-INF/books-servlet.xml (excerpt)

<bean id=”bookXmlView” class=”org.springframework.web.servlet.view.xml.MarshallingView”> <constructor-arg> <bean class=”org.springframework.oxm.xstream.XStreamMarshaller”> <property name=”autodetectAnnotations” value=”true”/> </bean> </constructor-arg> </bean>

Pretty standard. As you can see I’m scanning the com.stupidjavatricks.restexample package for annotated classes. I’m using a BeanNameViewResolver to keep the example simple, but you could get much more complex behavior using other ViewResolvers including a new favorite of mine: ContentNegotiatingViewResolver. The view I’m adding here is a MarshallingView, which takes POJOs and marshals them to XML, automatically. In order for this to work, I need to pass a Marshaller object to the MarshallingView constructor. I chose XStreamMarshaller because I’m familiar with XStream, but you could choose any of the other implementations including CastorMarshallerXmlBeansMarshaller, etc (Something to note about these marshallers is that they implement both the Marshaller and UnMarshaller interfaces.). You’ll notice that I set the autodetectAnnotations property to true. This will allow XStream to take advantage of any annotated classes on the classpath. This convenience comes at a price however, so if you wish you can explicitly tell XStream which annotated classes to look for. You would do this in the Spring XML context by removing the autodetectAnnotations property and passing a list of classes to the annotatedClasses property. Check the XStreamMarshaller API for more configuration settings.

Testing

You can build and run this any number of ways. I use Maven to build and Apache Tomcat to run it.

You can download the code here.

From the spring-rest-example-model project directory you can run mvn install. Then from within the spring-rest-example project directory mvn package. Check the target directory of the last project for the WAR file and deploy it to the Tomcat webapps directory and start the server. For a list of all authors and all books in this example you can browse to http://localhost:8080/spring-rest-example/booksearch/authors/ and http://localhost:8080/spring-rest-example/booksearch/books/ respectively.

Problems?

If you are having trouble getting the code to import or compile try one or both of the following:

  1. Go to Window->Preferences->Maven->Installations and choose your local Maven installation rather than the embedded Maven. I find that this reduces a some of the strange errors I used to get.
  2. Import spring-rest-example-model first and do a mvn install before importing spring-rest-example. I’ve noticed that Eclipse or STS will often throw a NullpointerException if I don’t follow these steps and attempt to import both projects at the same time.
Scroll to Top