Just REST, it's Easy With Spring and Jersey

Posted on Monday, February 28, 2011

0


In one of the products that we are working on, there is a need to expose the API as REST so that it can be consumed by the developer community and foster innovation. I did a few prototypes for the combination of dependency injection and REST with Restlet, Guice, Jersey and Spring and finally settled with Jersey and Spring primarily due to the easy configuration and tooling available around the two. Let us see how to easily build a REST enabled service with Jersey and Spring.

First, let us quickly create a simple webapp using mvn archetype:generate, if you are using maven 3 then you would see a huge number of options. Pick up 104 😉 ( at least this is the number for mvn 3.0.2)

104: remote -> maven-archetype-webapp (An archetype which contains a sample Maven Webapp project.)

Ok, once we have the web app, then we need to add the relevant dependencies to the pom.xml. These are the ones that I added,

<repositories>
		<repository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Repository for Maven</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
		<repository>
			<id>maven-repository.dev.java.net</id>
			<name>Java.net Maven 1 Repository (legacy)</name>
			<url>http://download.java.net/maven/1</url>
			<layout>legacy</layout>
		</repository>
	</repositories>


	<!-- Shared version number properties -->
	<properties>
		<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-core</artifactId>
				<version>${org.springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-beans</artifactId>
				<version>${org.springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-web</artifactId>
				<version>${org.springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-context</artifactId>
				<version>${org.springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring</artifactId>
				<version>${org.springframework.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-server</artifactId>
			<version>1.5</version>
		</dependency>
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-json</artifactId>
			<version>1.5</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.2</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.sun.grizzly</groupId>
			<artifactId>grizzly-servlet-webserver</artifactId>
			<version>1.9.8</version>
		</dependency>
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-client</artifactId>
			<version>1.5</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.sun.jersey.contribs</groupId>
			<artifactId>jersey-spring</artifactId>
			<version>1.2</version>
			<exclusions>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<finalName>justrest</finalName>
		<plugins>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>6.1.17</version>
			</plugin>

		</plugins>
	</build>

Notice, that we need to include some repositories from java.net. Also, notice that we have a dependency on jersey-spring. As of now, jersey-spring depends on the Spring 2.5.6 framework, hence we are excluding it so that we can continue to use 3.0.5.RELEASE without getting into any conflicts

<dependency>
			<groupId>com.sun.jersey.contribs</groupId>
			<artifactId>jersey-spring</artifactId>
			<version>1.2</version>
			<exclusions>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

Now, let us look at the changes that we would have to make in web.xml. The most significant portion of the web.xml is the servlet mapping for the jersey spring servlet which looks like this

<servlet>
		<servlet-name>REST</servlet-name>
		<servlet-class>
			com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
		<init-param>
			<param-name>com.sun.jersey.config.property.packages</param-name>
			<param-value>com.inphina.sample</param-value>
		</init-param>
		<init-param>
			<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
			<param-value>true</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<!-- Map /rest/* to Jersey -->
	<servlet-mapping>
		<servlet-name>REST</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

Next, we also include the standard listeners for Spring, so that it can create the beans, which are the following

<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>
	<listener>
		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>

All right, so far so good. It seems that we have all the infrastructure for REST in place. Now, assume that you have a service with the following definition which you want to expose as REST

package com.inphina.sample;

public interface SpringService {

	public void helloMe();

}

And, the implementation looks like this


package com.inphina.sample;

public class SpringServiceImpl implements SpringService {

	public void helloMe() {
		System.out.println("The spring service is being invoked");

	}

}

Let us put this as a bean in the 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" xmlns:context="http://www.springframework.org/schema/context"
	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">
	<bean id="springService" class="com.inphina.sample.SpringServiceImpl"> </bean>
</beans>

If you do a mvn jetty:run at this stage then may be it is all good (if you have been following the example closely) or you might get the following message


SEVERE: Exception occurred when intialization
com.sun.jersey.api.container.ContainerException: The ResourceConfig instance does not contain any root resource classes.
	at com.sun.jersey.server.impl.application.RootResourceUriRules.(RootResourceUriRules.java:103)
	at com.sun.jersey.server.impl.application.WebApplicationImpl._initiate(WebApplicationImpl.java:1184)
	at com.sun.jersey.server.impl.application.WebApplicationImpl.access$600(WebApplicationImpl.java:159)
	at com.sun.jersey.server.impl.application.WebApplicationImpl$12.f(WebApplicationImpl.java:700)
	at com.sun.jersey.server.impl.application.WebApplicationImpl$12.f(WebApplicationImpl.java:697)
	at com.sun.jersey.spi.inject.Errors.processWithErrors(Errors.java:193)

If you get the above error, then it means that you have not pointed the servlet property

<init-param>
			<param-name>com.sun.jersey.config.property.packages</param-name>
			<param-value>com.inphina.sample</param-value>
		</init-param>

to the right location. The right location is the one which has a jersey based REST class. Let us write the root resource now

The class looks like this


package com.inphina.sample;

import java.util.logging.Logger;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import com.sun.jersey.spi.inject.Inject;

@Path("/helloworld")
public class HelloWorldResource {

	@Inject
	private SpringService springService;

	private final Logger LOGGER = Logger.getLogger(HelloWorldResource.class.getName());

	@GET
	@Produces("application/json")
	public Person getSomeMessage() {
		springService.helloMe();
		return new Person("Vikas Hazrati", 36, "vhazrati@inphina.com");
	}

	@PUT
	@Consumes()
	public void setNewText(String newText) {
		LOGGER.info("do something interesting |" + newText + "|");
	}
}

A few things here. Look at the @Path annotation, this is the rest path through which we would be able to call the REST service.

The @GET annotation shows the method which would be invoked for a GET call. The @Produces(“application/json”) annotation shows that the returned value would be in JSON format so that clients can consume it as a JSON object.

If you notice that we have included the following dependency in our pom.xml

<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-json</artifactId>
			<version>1.5</version>
		</dependency>

The @Inject annotation is used to inject the SpringService into the REST root resource.

So that is it, we are good. Let us give it a test run and see how it works.

Execute mvn jetty:run and access the URL at the following location

http://localhost:8080/justrest/rest/helloworld

we get the response back as a JSON object

{“name”:”Vikas Hazrati”,”email”:”vhazrati@inphina.com”,”age”:36}

and on the console we would see that our spring service is being called


28 Feb, 2011 11:12:32 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.5 01/14/2011 12:36 PM'
2011-02-28 23:12:33.464::INFO:  Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
The spring service is being invoked

Entire source code for this project is present here.

Advertisements
Tagged: , , ,
Posted in: Java