Dec 01 2009
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 CastorMarshaller, XmlBeansMarshaller, 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:
- 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.
- Import
spring-rest-example-modelfirst and do amvn installbefore importingspring-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.
If you had trouble getting the example code to build, that’s because I forgot to include the Java version variable in the spring-rest-example-model project. It should work now.
Take a look at @ModelAttributte annotation. It might help you get rid of ModelAndView classes in every method.
Very good blog Tony!
There are a bit of syntactic sugar that you could do here with @ResponseBody. IMHO, it would also be idea to show a POST or PUT used with @RequestBody.
we are looking for someone to teach REST via online webcast. It will be 1.5 hours per week for about 10-15 weeks. Flexiblie hour based on your availability. If you are interested, please contact me.
Victor Liang
Nice Spring 3.0 REST support sample.
Since Dec 16th, Spring 3.0 is available on Maven central repository. (and servlet API 2.5 is available on central repository too).
No need to include additional definition.
Do note that with final 3.0.0, the artefact names are : spring-xxx.
I would suggest to actualize your pom to this : http://flex.scoutant.org/assets/pom.xml
spring-rest-example-model directory is unchanged : just run: ‘mvn install’
Then, in dir spring-rest-example, with the actualise pom, you can run ‘mvn install’.
Thanks. Yes, this blog was written on Dec 1. I’m now evaluating the GA release. There was a “show-stopper” bug in RC3 that forced me to back up to RC2, so I need to ensure it was fixed first.
For anyone not wishing to go through the trouble of changing all of your Spring dependencies in your POM, you can continue to use the Spring bundle repositories. All you need to do is change add the following “release” repository to your POM and then change your “spring.version” property to “3.0.0.RELEASE”.
IRT using Maven Central version of Spring dependencies, there are real reasons to use the EBR versions, primarily that the Spring EBR versions are tested by SpringSource QA and are also OSGi-enabled bundles (Maven Central are not OSGi-enabled).
The artifact names being spring-xxx are not related to the 3.0.0.RELEASE update, but instead to difference in naming conventions between Maven Central and Spring EBR versions. The names map like this:
Maven Central: spring-xxx
Spring EBR: org.springframework.xxx
The exception that I know of is spring-webmvc, which maps to org.springframework.web.servlet.
One other note: I had to explicitly add XStream to my pom.xml:
I also meant to add, great article! This presented the basics for Spring 3.0 REST simply and clearly. Other than getting the correct version of XStream (I had 1.2.2 to start, which doesn’t have the autodetectAnnotations property!), my own custom example ran on the first try!
Thanks very much for the example. I’m trying to integrate a C#.NET client against the REST service exposed by this example.
I want to change the response header to UTF-8.
I’ve been through a lot of forums that seem to think that the Dispatcher Servlet is hard-coded to ISO-8859-1, as follows:-
Content-Type: application/xml;charset=ISO-8859-1
Please let me know if we have any control over this at Controller level.
Thanks
Pat
@Pat: I think this may be an issue of the platform you are using (Windows right?). Anyway, I found several ways to change the charset, but the one I’m going to mention seems like the best option. Look at the Spring config file with the MarshallingView bean. Add the following property:
<property name=”contentType” value=”application/xml;charset=UTF-8″/>
This changes the entire Content-Type header, but that’s fine in this case since you know you want XML. It might also be a good idea to change the charater encoding of your SML marshaller – in my example the XStreamMarshaller.
<property name=”encoding” value=”UTF-8″/>
Give these a try.
Hi,
I built the war and deployed into tomcat 6.0.18. The java version is 1.6. After it is deployed, I got 404 books servlet not available.
Please help
Got it working using your later release sample.
Supernice article! Made it very easy to setup a project and get it running.
does the annotation only works with Spring MVC or does it support in Struts2 ?
I wasn’t looking for a web service but an example on how to configure web.xml and the controller with @PathVariables. Your example above helped a lot. Thanks.
Nice post!! It saved me a great deal of time when you mentioned including the prefix: BindingResult.MODEL_KEY_PREFIX. We were using Jaxb marshaller/unmarshaller and as soon as switching to XStream, the bug showed up. Spring needs to fix this. Thanks a lot Tony.