Friday, July 6, 2012

REST services with Spring 3 (XML, JSON)

As an alternative to webservices, we can use REST. Both REST and webservices have their differences. While in a previous post I wrote an example of webservices with Spring 3, I will post here one way of implementing REST using Spring 3. This example consists of a service that can return either XML or JSON depending on the URL invoked. You will be able to check the return values via a browser or a spring client test (which is provided in the example). The example also includes a web page built with extJS that fills a grid with data obtained via the service that return JSON data. Let's see it!


Versions used:
  • Spring 3.1.1-RELEASE
  • XStream 1.4.2 (XML parser)
  • Jackson 1.9.7 (JSON parser)
  • Maven 3


pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>ar.com.pabloExample</groupId>
 <artifactId>spring-rest-example</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>spring-rest-example Maven Webapp</name>
 <url>http://maven.apache.org</url>

 <dependencies>

  <!-- Spring dependencies -->

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>
  
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-oxm</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <!-- JSON parser -->
  
  <dependency>
   <groupId>org.codehaus.jackson</groupId>
   <artifactId>jackson-mapper-asl</artifactId>
   <version>1.9.7</version>
   <scope>compile</scope>
  </dependency>

  <!-- XML parser -->
  
  <dependency>
   <groupId>com.thoughtworks.xstream</groupId>
   <artifactId>xstream</artifactId>
   <version>1.4.2</version>
   <scope>compile</scope>
  </dependency>

  <!-- For testing purposes -->

  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
  </dependency>

 </dependencies>

 <build>
  <finalName>spring-rest-example</finalName>
  
  <plugins>
  
   <!-- With this you can start the server by doing mvn jetty:run -->
   <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>maven-jetty-plugin</artifactId>
    <version>6.1.26</version>
    <configuration>
     <scanIntervalSeconds>3</scanIntervalSeconds>
    </configuration>
   </plugin>
  
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>
   
  </plugins>
 </build>
</project>

Book.java

@XStreamAlias("book")
public class Book {

 private Integer id;
 private String name;

 //Getters and setters ommited but must exist
}

We use the annotation @XStreamAlias("book") so that the XML generated output for the root book tag is <book>, else, it would be <ar.com.pabloExample.model.Book> because of the way XStream handles XML transformation.

<list>
 <book>
  <id>0</id>
  <name>book nro. 1</name>
 </book>
 <book>
  <id>1</id>
  <name>book nro. 2</name>
 </book>
 <book>
  <id>2</id>
  <name>book nro. 3</name>
 </book>
</list>

BookController.java

@Controller
@RequestMapping("/book")
public class BookController {

 @RequestMapping(value="/names", method=RequestMethod.GET)
 public List<Book> getNames() {
  
  return returnData();
 }
 
 private List<Book> returnData() {
  
  ArrayList<Book> names = new ArrayList<Book>();
  
  Book b1 = new Book();
  Book b2 = new Book();
  Book b3 = new Book();
  
  b1.setName("book nro. 1");
  b1.setId(0);
  b2.setName("book nro. 2");
  b2.setId(1);
  b3.setName("book nro. 3");
  b3.setId(2);
  
  names.add(b1);
  names.add(b2);
  names.add(b3);
  
  return names;
 }
}

With those @RequestMapping annotations we are able to create a REST service so that it returns the getNames() return value whenever this address is invoked:
http://localhost:8080/spring-rest-example/rest/book/names


Books.java

public class Books {
 
 @JsonProperty("bookList")
 private
 List<Book> books;

 public List<Book> getBooks() {
  return books;
 }

 public void setBooks(List<Book> books) {
  this.books = books;
 }
}

This entity is just a wrapper for our List<Book>, my JSON example wouldn't work managing the list directly.
Also, the @JsonProperty("bookList") annotation exists in order to map the return "bookList" to the "books" attribute.

{"bookList":[{"id":0,"name":"book nro. 1"},{"id":1,"name":"book nro. 2"},{"id":2,"name":"book nro. 3"}]}


web.xml

<!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>Archetype Created Web Application</display-name>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath*:applicationContext.xml</param-value>
 </context-param>

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

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

 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/rest/*</url-pattern>
 </servlet-mapping>

</web-app>

This is an usual Spring web configuration context. Every http://localhost:8080/spring-rest-example/rest/* request will be attended by the spring-servlet.xml


spring-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:mvc="http://www.springframework.org/schema/mvc"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.1.xsd">

 <mvc:annotation-driven/>

 <context:component-scan base-package="ar.com.pabloExample.controller" />

 <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="order" value="1" />
  <property name="mediaTypes">
   <map>
    <entry key="json" value="application/json" />
    <entry key="xml" value="application/xml" />
   </map>
  </property>

  <property name="defaultViews">
   <list>
    <!-- JSON View -->
    <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />

    <!-- XML View -->
    <bean 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>
   </list>
  </property>
 </bean>

</beans>

Here, we configure ContentNegotiatingViewResolver to either accept XML and JSON when answering a request.
http://localhost:8080/spring-rest-example/rest/book/names - returns XML (default when no extension)
http://localhost:8080/spring-rest-example/rest/book/names.xml - returns XML
http://localhost:8080/spring-rest-example/rest/book/names.json - returns JSON


applicationContext.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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

</beans>

This XML file is mandatory in the Spring configuration. We put nothing here.


Let's test the services!


test-beans.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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">  
      <property name="messageConverters">
       <list>
           <bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
            <property name="marshaller" ref="xstreamMarshaller"/>
            <property name="unmarshaller" ref="xstreamMarshaller"/>
           </bean>
           <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
       </list>
      </property>
    </bean>
    
 <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
  <property name="aliases">
         <props>
             <prop key="book">ar.com.pabloExample.model.Book</prop>
         </props>
     </property>
 </bean>

</beans>

The RestTemplate class has configuration for both XML and JSON message converters.
For XML marshalling we use XStream, and configure an alias so it is able to understand the <book> refers to <ar.com.pabloExample.model.Book>


RestClientTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test-beans.xml" })
public class RestClientTest {

 @Autowired
 @Qualifier("restTemplate")
 private RestTemplate restTemplate;
 
 @Test
 public void restJsonClientTest() {
  
  Books booksAux = restTemplate.getForObject("http://localhost:8080/spring-rest-example/rest/book/names.json", Books.class);
  List<Book> books = booksAux.getBooks();
  
  Assert.assertNotNull(books);
  Assert.assertTrue(books.size() > 0);
 }
 
 @SuppressWarnings("unchecked")
 @Test
 public void restXmlClientTest() {
  
  List<Book> books = (ArrayList<Book>) restTemplate.getForObject("http://localhost:8080/spring-rest-example/rest/book/names.xml", List.class);
  
  Assert.assertNotNull(books);
  Assert.assertTrue(books.size() > 0);
 }
}

Run mvn jetty:run to start jetty web server so that the webservice is exposed, then you can run RestClientTest doing mvn test.

http://localhost:8080/spring-rest-example/rest/book/names - returns XML (default when no extension)

http://localhost:8080/spring-rest-example/rest/book/names.xml - returns XML
http://localhost:8080/spring-rest-example/rest/book/names.json - returns JSON


You can access http://localhost:8080/spring-rest-example/example.html too to see an extJS data grid example that is filled with the JSON service we've just deployed.


Get the complete code!


https://subversion.assembla.com/svn/pablo-examples/spring-rest-example/

  1. First, run mvn jetty:run to start jetty web server
  2. Then mvn test to run JUnit tests


Finally


I think there's too much boilerplate configuration to enable REST services with Spring, unless you're already using Spring to build your app. Maybe other products like RESTEasy are more suited if we don't want to use Spring for our webapp but still want to have services exposed via REST...

For more information about mvc:annotation-driven tag, @Controller annotation, @RequestMapping see:
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html

16 comments:

  1. Wish your example had used jaxb instead of xtream

    ReplyDelete
  2. I believe a JAXB approach is better (no additional jar, more standard approach...) but I had some problems using JAXB, when I tried XStream it worked without any issue.

    ReplyDelete
    Replies
    1. Can you tell how it can be by JAXB because i tried but wasn't to can my result getting 406 Error

      Delete
    2. Take a look at this other similar post I wrote some months after that last answer:
      http://pfelitti87.blogspot.com/2013/05/restful-generic-crud-with-spring-3-jpa.html

      Delete
  3. Excellent! Thank you! That's exactly the sort of thing we'll need in the future! You saved us much time and effort!

    ReplyDelete
  4. Thanks, everything works as expected, good job!

    ReplyDelete
  5. Thanks for putting such a nice example ....

    ReplyDelete
  6. Hi would you be able to provide an example how to use RestTemplate to POST an ArrayList , in this case how do you POST your Books object?

    ReplyDelete
  7. Hi, sorry for the delay, been really busy lately... You may try to use the "postForObject" method instead of "getForObject" and use a wrapper for your list in case you cannot manage to post the array list directly.

    ReplyDelete
  8. Hi,
    I am trying to download this example from :
    https://subversion.assembla.com/svn/pablo-examples/spring-rest-example/

    using svn tourtoise, but it asked me username and password.

    Can you please send me a zip of this example ?
    thanks lot.

    ReplyDelete
  9. Hi, you should be able to download it because the view mode is available for everyone. Can you access via your browser?

    ReplyDelete
  10. Easily, the article is actually the best topic on this registry related issue. I fit in with your conclusions and will eagerly look forward to your next updates. Just saying thanks will not just be sufficient, for the fantasti c lucidity in your writing. I will instantly grab your rss feed to stay informed of any updates. Notary Service

    ReplyDelete
  11. Thanks! very nice article. Could you please let me know how to post a request in RestClientTest.
    I am new to this and running in to issues. Appreciate your help!!

    ReplyDelete
    Replies
    1. Hi, you can take a look at this post where I made some post requests:

      http://pfelitti87.blogspot.com.ar/2013/05/restful-generic-crud-with-spring-3-jpa.html

      Hope it helps!

      Delete
  12. I thought it was going to be some boring old post, but it really compensated for my time. I will post a link to this page on my blog. I am sure my visitors will find that very useful. Notary Service

    ReplyDelete
  13. nice article thanks, how do I modify the rest service to use ssl certs

    ReplyDelete