Integrating JBoss RESTEasy and Spring MVC
Building websites is a tough job. It's even tougher when you also have to support XML and JSON data services. Developers need to provide increasingly sophisticated AJAXy UIs. Marketing groups and other business units are becoming more savvy to the benefits of widgets and web APIs. If you're a Java developer who needs to implement those sexy features, you're likely going to accomplish that work with a dizzying variety of frameworks for web development, data access and business logic.
The Spring Framework has a strong presence based on the premise of seamless (no pun intended) integration of all of those frameworks. The Spring framework integrates with a host of JEE standard technologies, such as EJBs and JSP. Spring MVC is a sub-project of the larger Spring Framework that has its own Controller API and also integrates other web development frameworks such as JSF, Struts and Tiles.
While the Spring Framework also integrates with new JEE technologies as they develop, however, for a variety of reasons the Spring framework has not integrated with the tour de force JAX-RS standard which delivers an API for constructing RESTful services. There are six implementations of the JAX-RS standard, and each provides some level of integration with Spring. Most of those integrations work with the Spring framework proper, but don't take advantage of the benefits of Spring MVC. JBoss RESTEasy integrates with both the Spring Framework proper and also with the Spring MVC sub-project.
In this article, we're going to explore how to use RESTEasy along with Spring MVC application. We'll deep dive into the internals of Spring MVC, we'll discuss JAX-RS and how they related to MVC web development. We'll also touch on quite a few technologies beyond Spring MVC and RESTEasy, including Jetty and maven. We're also going to discuss theoretical concepts relating to REST and Dependency Injection. This article has to cover quite a bit of ground and you'll be gaining quite a few tools you can use to develop complex web applications. If you follow this article, you'll be constructing an end-to-end web application, however, feel free to skim the article to find material that's relevant to you.
REST and JAX-RS
REST has been an increasingly trendy topic over the last three years. We as a development community have been looking at REST as an effective way to perform distributed programming and data-oriented services. In fact, the Java community's REST leaders got together and created a standard spec to standardize some RESTful ideas in JSR 311 - JAX-RS the Java API for XML and RESTful Services. The focus of JAX-RS was to create an API that Java developers could use to perform RESTful data exchanges. However, the Java community quickly saw the similarities between JAX-RS and MVC (Model View Control) infrastructures. James Strachan, a long time Java community member and open source contributor (to things like DOM4J, Groovy - he created the language, and recently the Apache Camel and CXF ESBs) suggested that JAX-RS as the one Java web framework to rule them all?. Jersey, the production ready JAX-RS reference implementation, has a built in JSP rendering mechanism. The RESTEasy community built a similar mechanism in HTMLEasy.
The Jersey and and HTMLEasy approaches work well for simpler websites, but they don't solve some of the more complex needs of an application. If you want more complex functionality, you'll need a more sophisticated web-development platform, such as Spring MVC. A combination of Spring MVC and RESTEasy will have the following benefits compared to the simpler approaches:
- Session based objects
- Freedom of choice - chose the right tool for the job
- Spring MVC integrates with a whole bunch of MVC frameworks, including Spring MVC, Struts2 and now RESTEasy
- Spring MVC integrates with a whole bunch of View frameworks, including JSP, JSF, Tiles and much more
- Integrated AJAX components - the freedom of choice can make end-to-end AJAX calls a breeze, assuming you chose the appropriate framework
- More control over URL mapping
This article tackles some more advanced topics. If you want some relevant background, we have a reference section at the end of this article. Before we take a look at code, let's take a more in depth view of Spring MVC.
Spring MVC
Spring MVC is broken down into three pluggable sub-systems:
Handler Mapping - Map a URL to Spring Bean/Controller. Spring allows quite a few methods to perform this mapping. It can be based on the name of a Spring bean, it could be a URL to bean map, it could be based on an external configuration file or it could be based on annotations. Handler Mappings allow you to configure complex mappings without resorting to complex web.xml files.
Handler Adapter - Invoke the Controller. Hander Adapters know what type of spring beans they can call and performs invocations on the types of beans it knows about. There are Handler Adapters for Spring MVC classic, spring @MVC, Struts, Struts2, Servlets, Wicket, Tapestry, DWR and more.
View Mapping - Invoke the View. View Mappers know how to translate a logical view name produced by a Controller into a concrete View implementation. A name like "customers" may translate into any of the following technologies: JSP/JSTL, JSF, Velocity, FreeMarker, Struts Tiles, Tiles2, XSLT, Jasper Reports, XML, JSon, RSS, Atom, PDF, Excel, and more
RESTEasy plugs into Spring MVC in all three sub-systems.
JAX-RS Resources/Controllers are defined by annotations; therefore RESTEasy provides a ResteasyHandlerMapper that knows how to convert a URL to a RESTEasy managed JAX-RS controller method.
Once RESTEasy determines which method to invoke, the ResteasyHandlerMapping performs the invocation. The invocation can either be an object, which invokes the default JAX-RS behavior which transforms the resulting Object to a Represetation such as XML or JSON. Additionally, you return a traditional Spring ModelAndView which can refer to a logical view name and a map of data to be rendered by the View.
The default JAX-RS behavior creates a ResteasyView which uses JAX-RS's configurable MessageBodyReader and MessageBodyWriter transformation framework. RESTEasy can produce XML and JSON using JAXB, but can be configured to use other view technologies such as Jackson, which is a performant and flexible JSON provider, Flex AMF.
This separation of Controller and View concepts allows you to mix and match your Controller and View technologies. RESTEasy Resources can call any Spring managed Views and other Controller technologies can be rendered by a ResteasyView. You can either use RESTEasy as your sole MVC framework, if it fits your needs, or you can augment an existing Controller infrastructure with data services provided by RESTEasy.
Just as importantly, you can leverage all of the other functionality that Spring provides, such as DAO abstraction, transaction management and AOP.
Your First SpringMVC/RESTEasy Application
Before we start reviewing the project, let's review a quick checklist of items we will be reviewing. The project files fall into two categories: configuration and source code. All of the code that will be covered is available in the RESTEasy repository and can be downloaded (as a tar.gz file), or browsed.
Here is a list of files that each category will require.
Configuration Files:
- web.xml - servlet configuration with Spring MVC artifacts - Spring MVC's DispatcherServlet, and map to /contacts/*
- springmvc-servlet.xml - a Spring application context configuration with all of the Spring beans this project needs, including RESTEasy setup (one line) and JSP configuration
- pom.xml - maven 2 dependency configuration, including required repositories, RESTEasy dependencies and embedded Jetty setup
Source Code:
The code we're going to show you can be broken down into four layers:
- Controller - Controlling the flow between the HTTP request, the Model and the View
- ContactsResource.java - a RESTful Controller with JAX-RS annotations and some traditional HTML controller methods. It will be annotated with Spring's @Controller and @Autowired annotations as well.
Model - the domain model and service objects in our case. In our case, we have 2 domain objects: Contact and Contacts; and 1 Service object: ContractService
- Contact.java - a JAXB domain object with contact information
- Contacts.java - a JAXB wrapper object that wraps a Contact List
- ContactService.java - a Map based repository of Contact instances
View - How the domain model is transformed for consumer use. JAX-RS performs automated conversion to XML based on annotations on our domain model. We'll be using JSP for object to HTML conversion.
- contacts.jsp - a bare bones HTML view of our Contacts
- Test - JAX-RS provides quite a bit of functionality, we're writing quite a bit of code, and all of that is wrapped in quite a bit of configuration. This article will focus on testing our code, configuration and deployment in an automated JUnit test.
- ContractTest.java - a RESTEasy ReSTful Unit test for the ContractsResource functionality, embedded server included
Core RESTEasy/Spring MVC artifacts
web.xml
Spring MVC's entry point is the DispatcherServlet. There are two parts in setting up the DispatcherServlet, the first is to map the servlet to the URL pattern which must follow the rules specified in Section 11.2 of Servlet API Specification. The next step is configure the behavior of the the servlet by providing the configuration file which we call 'springmvc-servlet.xml'. By default, DispatcherServlet looks for a configuration file at "WEB-INF/{servlet-name}-servlet.xml" to find it's configuration, but we're going to use a Spring configuration from the classpath so that the configuration can be reused later in our junit test case.
<?xml version="1.0"?> <!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> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/contacts/*</url-pattern> </servlet-mapping> <web-app>
All requests will be forwarded to the Spring MVC DispatcherServlet. One can get much more sophisticated, but this is one of the simplest simplest web.xml you can create to integrate with Spring. Note that there isn't any reference here to a RESTEasy servlet. Other JAX-RS/Spring integrations require you to have an implementation specific Servlet to serve XML or JSon and a separate Spring MVC DispatcherServlet mapping to server HTML requests. RESTEasy integrates with DispatcherServlet to allow Spring MVC to direct the URL to either RESTEasy Resources or Spring MVC Controllers.
Next, let's take a look at the Spring MVC configuration.
springmvc-servlet.xml
In our basic project, there are five things we need to do in the springmvc-servlet.xml file.
- Register the Spring <context/> namespace
- Register the package(s) to scan for Spring MVC annotations.
- Configure the context annotation processor.
- Import the springmvc-resteasy.xml configuration file which specifies the default RESTEasy/Spring MVC integration Spring beans.
- Configure the ViewResolver bean to configure the presentation layer to use JSP.
Let's inspect the springmvc-servlet.xml file and focus on each of the above items. The springmvc-servlet.xml file itself is pretty short and shows off some of the features from Spring 2.5:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <context:component-scan base-package="org.jboss.resteasy.examples.springmvc" /> <context:annotation-config /> <import resource="classpath:springmvc-resteasy.xml" /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
Demystifying the Spring Configuration
Spring allows for custom namespaces to reduce the verbosity of the configuration files. We make use of the <context/> namepsace by registering it in lines 3 and 4. Line 6 (component-scan) informs spring about which package(s) we want to scan and create the custom component object instances, such as controllers and service objects. We tell Spring about the packages we're interested in by using the <context:component-scan/> custom namespace and set the base-package attribute with the packages we're interested in (org.jboss.resteasy.examples.springmvc ). Later on, you'll see that we're going to be using two Spring annotations that allow the Spring runtime to glean Dependency Management information directly from the object itself: @Controller and @Service.
Line 7 (annotation-config) tells Spring that our application will be using annotations on how to configure the beans created by the (component-scan) operation of line 6. Spring looks for annotations such as Spring's @Autowired and @Required; JEE's @Resource; and JPA's @PersistenceContext and @PersistenceUnit to describe dependencies between bean instances. annotation-configSpring also looks for life-cycle annotations such as JSR 250's @PostConstruct and @PreDestroy. Our environment requires a dependency between our Controller and Service objects, and the annotation-config declaration will assist us to configure that relationship in Java code.
Line 8 ( import) is all the XML that is necessary to configure a RESTEasy environment in Spring MVC. The nice thing about the integration with RESTEasy is that most of the configuration is done for you within an embedded configuration file called springmvc-resteasy.xml.Lines 9-14 tell Spring how we intend to handle the rendering of our presentation layer. In our case, we want to use JSTL views that translate view names (such as "contact") to a JSP page found in the /WEB-INF/ directory (specifically /WEB-INF/contacts.jsp in our case). For more information about setting up Spring views, take a look at the spring documentation.
Next, let's take a look at how you can mix and match Spring and JAX-RS annotations in a Controller/Resource.
ContactsResource.java
MVC Controllers control the flow between the Model and the View. Resource is REST's equivalent to Controllers, and we'll be using the term Resource and Controller interchangably. In our case, our resource handles requests to /contracts and /contracts/{id}. Our ContractsResource must perform quite a few functions on those two URL templates:
- Retrieve all Contacts - Display the results in either HTML, XML or JSon. For clarity, we'll break out the data oriented functionality (XML and JSon) from the user oriented functionality (HTML) into two distinct URLs - /contacts for HTML and /contacts/data for XML and JSon. REST allows a client to select which format it prefers to receive the data in through a process called Content Negotiation. Content Negotiation can happen through HTTP headers, URI or query parameters. Our ContractsResource will use different URIs to differentiate between data oriented and user views, and will use HTTP headers to differentiate between XML and JSon data views.
- Save a Contact - Create or Updating data is a pretty standard requirement. The Save a Contact functionality mirrors the Content Negotiation needs of Retrieve all Contacts. User oriented data exchange comes in the form of HTML form data, and data oriented exchange usually occurs in XML and JSon. These differing requirements require ContractResource to have two distinct JAX-RS Java methods; we'll also separate the URLs for clarity purposes.
- View a Contact - We'll create a single view for viewing a single contact that returns XML or JSon. We leave the user oriented view as an exercise for the reader.
Here's another view of our requirements:
Functionality | URL | Format | Java Method |
User Oriented View All | /contacts | HTML | viewAll() |
Data Oriented View All | /contacts/data | XML or JSon | getAll() |
User Oriented Save | /contacts/ | Form data | saveContactForm() |
Data Oriented Save | /contacts/data | XML or JSon | saveContact() |
Data Oriented View Single | /contacts/data/{lastName} | XML or JSon | get() |
Note that we mixed and matched HTML and data oriented functionality in this requirement. Now that we have our requirements in place, let's take a look at the ContactsResource code. There are quite a few new Spring and JAX-RS annotations which we'll explain right after the code:
@Controller @Path(ContactsResource.CONTACTS_URL) public class ContactsResource { public static final String CONTACTS_URL = "/contacts"; @Autowired ContactService service; @GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Path("data") public Contacts getAll() { return service.getAll(); } @GET @Produces(MediaType.TEXT_HTML) public ModelAndView viewAll() { // forward to the "contacts" view, with a request attribute named // "contacts" that has all of the existing contacts return new ModelAndView("contacts", "contacts", service.getAll()); } @PUT @POST @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Path("data") public Response saveContact(@Context UriInfo uri, Contact contact) throws URISyntaxException { service.save(contact); URI newURI = UriBuilder.fromUri(uri.getPath()).path(contact.getLastName()).build(); return Response.created(newURI).build(); } @POST @PUT @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_HTML) public ModelAndView saveContactForm(@Form Contact contact) throws URISyntaxException { service.save(contact); return viewAll(); } @GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Path("data/{lastName}") public Contact get(@PathParam("lastName") String lastName) { return service.getContact(lastName); } }
- @Controller tells the Spring runtime that it needs to create an instance of ContractsResource at startup time. Do you remember the component-scan directive that was used in the Spring configuration section? The combination of the directive and the annotation tell Spring that a singleton instance of ContractsResource must be created at startup. Spring has a more generic @Component, but the use of @Controller allows for more precise definition of bean usage and also allows for future upgrades that involve AoP to create more precise targeting. While @Controller is usually associated with Spring @MVC annotated controllers and not other Controller infrastructures, but even thought it's not a Spring MVC controller, we use it to tell Spring that this indeed is a Controller That association of @Controller to Spring MVC annotated controllers is a loose coupling in the Spring runtime. We'll use JAX-RS annotations to configure the handling of URL and HTTP handling behavior. You could theoretically add additional Spring @MVC annotations such as @RequestMapping (which is an equivalent of JAX-RS @Path) to our ContactsResource, if you really wanted to
- @Autowired tells the Spring runtime that instances of ContractResource require an instance of ContactService. We'll be coding the ContactService later in this article. You can take a look at the Spring reference documentation for more information about @Autowired and @Controller.
- The last Spring artifact that we use is ModelAndView: It is Spring MVC's encapsulation of which logical View to use and which Model variables should be passed into the View. In our case, we're going to create a Model variable called "contacts" that is a List of all Contact objects we have in the system. We're passing that variable to the a logical view named "contacts" which will map to "/WEB-INF/contacts.jsp" based on the Spring configuration that we previously discussed.
- @Path tells the RESTEasy (or other JAX-RS environments) how to map URLs to java methods. Adding @Path at the class level tells, in our case "/contacts", indicates that all methods must be prefixed with that URL. The @Path value can either be a hard coded URL such as "/contacts" or it can be a URI template such as "data/{lastName}". You can even specify regular expressions for more sophisticated filtering in the URI template.
- @GET, @PUT and @POST are used in combination with @Path to indicate which specific HTTP methods are handled by individual Java methods
- @Produces and @Consumes are used to further filter how a request should be handled based on content negotiation based on the Accept and Content_Type HTTP header. JAX-RS provides a set of default mime type values in the MediaType class.
- @PathParam is a method parameter annotation that indicates how a URI template variable is mapped to a method parameter. There are quite a few other method parameter level annotations that you could use to map HTTP headers, cookies, query parameters and form parameters to member variables
- @Context is an interesting JAX-RS parameter that allows dependency injection of request level information such as HttpRequest, HttpResponse and UriInfo (which as you can probably guess encapsuldates information about the request URI). It's important to note that Spring by default manages beans such as ContactsResource as a singleton; if ContactsResource was a Prototype or Request scoped bean, you would be able to use the @Context annotation on member variables in addition to method variables. For more on Spring scoping see the Spring Framework documentation.
Additional Artifacts
pom.xml
Our pom.xml includes dependency management, description of required Maven repositories, a description of which JDK we're going to use and a Jetty web server configuration. We'll cover the repository selection, the dependencies specific to RESTEasy and a jetty-maven integration
External Repositories
<repositories> <repository> <id>jboss</id> <name>jboss repo</name> <url>http://repository.jboss.org/maven2</url> </repository> <repository> <id>scannotation</id> <url>http://scannotation.sf.net/maven2</url> </repository> <repository> <id>java.net</id> <url>http://download.java.net/maven/1</url> <layout>legacy</layout> </repository> <repository> <id>maven repo</id> <name>maven repo</name> <url>http://repo1.maven.org/maven2/</url> </repository> </repositories>
Project Dependencies
Now that we've informed Maven which additional repositories are required, we can now include the dependencies the our sample project will require. The <dependencies/> section of the pom.xml file, should include the following two dependencies for Spring and RESTEasy functionality - resteasy-spring and resteasy-jaxb-provider:
<dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-spring</artifactId> <version>1.2.RC1</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> <version>1.2.RC1</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.15</version> <scope>test</scope> </dependency> <!-- Other dependencies ... --> </dependencies>
The resteasy-spring dependency includes the adapter that integrates RESTEasy into Spring's MVC and provides most the required Java dependencies for RESTEasy and Spring. It also contains Spring configuration needed within the embedded spring-resteasy.xml file that will be used in the Spring configuration section. The other RESTEasy dependency that's included, resteasy-jaxb-provider, contains classes that convert the payload into various formats before sending it to the client.
The last dependency to focus on is the maven-jetty-plugin which allows us to easily startup our project in a Jetty webserver environment.
Note: If you're follow the link above to the RESTEasy repository's version of pom.xml, you will have to modify the version of resteasy-spring and resteasy-jaxb-provider to the latest version that has been deployed, specifically 1.2.RC1 at the time this article was written. The RESTEasy repository contains a soon-to-be-deployed version number which will not work unless you build the entire RESTEasy project.
Maven Jetty Plugin
<build> <finalName>resteasy-springMVC</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.15</version> <configuration> <contextPath>/</contextPath> <scanIntervalSeconds>2</scanIntervalSeconds> </configuration> </plugin> ... </plugins> </build>
Contact.java
Our DTO is going to be deceptively simple. It will perform a dual responsiblity of JAXB XML binding and Form parameter binding. Both sets of functionality will be configured through annotations and will be managed through JAXB and JAX-RS:
import javax.ws.rs.FormParam; import javax.xml.bind.annotation.XmlRootElement; @XMLRootElement public class Contact { private String firstName, lastName; // default constructor for JAXB (also required by JPA/Hibernate if you use them) public Contact(){} // helper constructor for our Controller/Service operations public Contact(String firstName, String lastName){ this.firstName = firstName; this.lastName = lastName; } @FormParam("firstName") public void setFirstName(String firstName) { this.firstName = firstName; } public String getFirstName() { return firstName; } @FormParam("lastName") public void setLastName(String lastName) { this.lastName = lastName; } public String getLastName() { return lastName; } // equals and hasCode are added for the Map based Service object public boolean equals(Object other){ .. } public long hashCode(){ .. } }
The annotation on the setters tells JAX-RS to bind any incoming form parameters to the appropriate setter. The @XMLRootElement annotation is enough to tell JAXB that the Contract object must be bound to getters and setters must be bound to an XML document that will look like:
<contact> <firstName>Richard</firstName> <lastName>Burton</lastName> </contact>
Contacts.java
The Contacts class is a simple wrapper around a List of Contact instances:
@XmlRootElement public class Contacts { private Collection<Contact> contacts; public Contacts() { this.contacts = new ArrayList<Contact>(); } public Contacts(Collection<Contact> contacts) { this.contacts = contacts; } @XmlElement(name="contact") public Collection<Contact> getContacts() { return contacts; } public void setContacts(Collection<Contact> contact){ this.contacts = contact; } }
<contracts> <contract> <firstName>Richard<firstName> <lastName>Burton</lastName> </contract> <contract> <firstName>Solomon<firstName> <lastName>Duskis</lastName> </contract> </contracts>
ContactService.java
@Service public class ContactService { private Map<String, Contact> contactMap = new ConcurrentHashMap<String, Contact>(); public void save(Contact contact){ contactMap.put(contact.getLastName(), contact); } public Contact getContact(String lastName){ return contactMap.get(lastName); } public Contacts getAll() { return new Contacts(contactMap.values()); } }
- Notice the use of Spring's @Service annotation. Do you remember the component-scan directive that was used in the Spring configuration section? The combination of the directive and the annotation tell Spring that a singleton instance of ContractService must be created at startup. Spring has a more generic @Component, but the use of @Service allows for more precise definition of bean usage and also allows for future upgrades that involve AoP to create more precise targeting.
- Notice the use of ConcurrentHashMap. It's a JDK 1.5 addition that adds performance in multi-threaded environments. It's an easy way to boost performance in distributed REST applications
contacts.jsp
We've explored the Model and Controller aspects of MVC. The last piece to the puzzle is the View. Most JAX-RS based interactions perform a more automated conversion of objects like our Contact to a data-oriented view, such as XML or JSon. Traditionally, Java EE MVC has been done with a more manual View management with languages such as JSP. Our JSP will take a Contracts instance created in ContractsResources.viewAll() and render it in basic HTML:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <body> <h2>Hello Contacts!</h2> <div id="contact-data" class="contacts"> <c:forEach var="contact" items="${contacts.contacts}"> <span class="contact">Hello <a href="/contacts/data/${contact.lastName}">${contact.firstName} ${contact.lastName}</a></span> </c:forEach> </div> <p>Save a contact, save the world:</p> <form action="/contacts" method="post"> First Name: <input type="text" name="firstName" /><br> Last Name: <input type="text" name="lastName" /><br> <input type="submit" value="submit"/> </form> </body> </html>
This JSP loops over all contacts and adds links to their data-oriented View. It also creates a simple HTML form for creating a new Contact. While this JSP is simple, it will help us exercise three of our ContactsResource Controller: viewAll(), .saveContactForm(), and get(). It could also be a spring board for more complicated AJAX/JSon interaction, but that's beyond the scope of this article.
The code and configuration is now complete, so let's run this project!
Jetty
Running Jetty is rather simple. You've seen most of the details of the pom.xml when we previously discussed it. Running jetty through maven involves running the following command:
mvn jetty:run
That command will launch Jetty, and allow you to browse our project at http://localhost:8080/contacts. Add a few contacts, and view them either as a group at /contacts in HTML, as a group in XML at /contact/data, or individually as XML by following the links found at /contacts.
Congratulations. You now have a running Spring MVC/RESTEasy application. We need one more thing to consider this application complete: a JUnit test.ContractTest.java
RESTEasy provides a mechanism for easily launching a Spring MVC/RESTEasy application. RESTEasy also comes with a robust REST client framework. This article will cover bits and pieces of the test, but you can view the entire code in the RESTEasy SVN.
To start, we're going to set up an interface that the RESTEasy client can use to create a client for our application. It consists of abstract methods annotated with JAX-RS annotations:
@Path(ContactsResource.CONTACTS_URL) public interface ContactProxy { @Path("data") @POST @Consumes(MediaType.APPLICATION_XML) Response createContact(Contact contact); @GET @Produces(MediaType.APPLICATION_XML) Contact getContact(@ClientURI String uri); @GET String getString(@ClientURI String uri); }
All methods on ContactProxy inherit the ContactsResource.CONTACTS_URL path ("/contacts") as the root URL, just like a server-side JAX-RS resource. This interface's has three methods:
- Create a contact - the createContact method maps to a POST to "/contacts/data". The method accepts a Contact object which will be converted to XML before it's sent to the server. The result is a JAX-RS Response object which contains the response status and headers. One of those headers includes the LOCATION of the new contact
- Get an XML Contact - Given a URL to a Contact, such as the URL returned by the createContact method's response's LOCATION header, GET an XML response and create a Contact object from it.
- Get a Response as a String - Given a URL, such as a Contact URL or anything else on the server, retrieve a String result.
This interface will be used by RESTEasy to construct a concrete instance that uses the JAX-RS annotations to perform the actual HTTP calls. Next, let's create the embedded server and use RESTEasy to create that instance of a ContactProxy:
private static ContactProxy proxy; private static TJWSEmbeddedSpringMVCServer server; public static final String host = "http://localhost:8080/"; @BeforeClass public static void setup() { server = new TJWSEmbeddedSpringMVCServer("classpath:springmvc-servlet.xml", 8080); server.start(); RegisterBuiltin.register(ResteasyProviderFactory.getInstance()); proxy = ProxyFactory.create(ContactProxy.class, host); } @AfterClass public static void end() { server.stop(); }
JUnit invokes methods annotated by @BeforeClass before any test methods run. Methods annotated by @AfterClass are triggered by JUnit before after all test methods are complete. In our case, the setup method will instantiate a server that contains a SpringMVC Servlet on port 8080 that is configured by the same Spring XML configuration file we used in Jetty. It also invokes the two lines of code required to create a RESTEasy client. RegisterBuiltin sets up the RESTEasy run time, and must be run one time per client. ProxyFactory.create tells RESTEasy to read the annotations on the ContactProxy interface and to create a Java Proxy instance that knows how to perform the HTTP requests we'll need for our test:
@Test public void testData() { Response response = proxy.createContact(new Contact("Solomon", "Duskis")); String duskisUri = (String) response.getMetadata().getFirst(HttpHeaderNames.LOCATION); Assert.assertTrue(duskisUri.endsWith(ContactsResource.CONTACTS_URL + "/data/Duskis")); Assert.assertEquals("Solomon", proxy.getContact(duskisUri).getFirstName()); ... }
This test creates a new Contact, checks the server's response to make sure that the URL is consistent with the test's expectations. It then re-retrieves the Contact and confirms that the firstName is indeed what was sent sent in. While this is a pretty trivial looking test, it performs quite a bit of HTTP activity and business logic.
Conclusion
This article discussed quite a bit of philosophy and design considerations in building a RESTful web application with RESTEasy and Spring MVC. We also built an end to end application with RESTEasy, Spring MVC, Maven, Jetty and JUnit. Even though the content in this article was significant, the code presented here is relatively short compared to other Java alternatives. We touched on subjects like designing REST Applications, creating Spring applications, the RESTEasy client infrastructure and testing RESTful applications. Each of those subjects merit their own articles. There were also other subjects that we simply couldn't fit into this article (as long as it is), including JavaScript to the toolkit to allow closer integration between the browser and your RESTful application, integrating with Flex and more. The code presented in this article can serve as a spring board (again, no pun intended) for all of those ideas.
About the Authors
Solomon Duskis
Solomon Duskis is a Senior Manager at SunGard Consulting Services. He's been developing for 22 years -- 12 years in professional capacity. He has experience in various industries such as Finance, Media, Insurance and Health. He contributes to Open Source projects such as JBoss Resteasy and the Spring framework. He is a published author of Spring Persistence - A Running Start, and the upcoming book Spring Persistence with Hibernate.
Richard Burton
Richard Burton is the co-founder of a small independent consulting firm called SmartCode LLC. He is an Open Source fanatic with over 10 years of experience in various industries such as Automotive, Insurance, Finance and fondly remembers the .com era. In his spare time, he contributes to Open Source projects such as SiteMesh 3, Struts 2, and more.
Reference
REST
- Roy Fielding's REST Thesis - Architectural Styles and the Design of Network-based Software Architectures(December 2000)
- Bill Burke's (August 2008 - DZone)
- How to GET a cup of coffee (October 2008 - InfoQ)
- Roy Fielding REST APIs Must Be Hypertext Driven (October 2008 - Untagled Roy's blog - take a look at the URI: roy.gbiv.com )
JAX-RS
- Bill Burke's (September 2008 - DZone)
- An overview of JAX-RS 1.0 Features
- James Strachan's JAX-RS as the one Java web framework to rule them all? (January 2009 - James' blog)
- RESTEasy project
- A blog about Spring + RESTEasy
- Getting Started with RESTEasy
- Spring 2.5 reference
- Oh, just search google for "spring framework"
- Spring MVC Reference
- Spring MVC Step By Step
- Spring MVC Tutorial