This page shows you how to quickly and easily create a read-only Spring based RESTful Web Service / Application.
To keep things simple I will not be covering the “front-end” view technologies. Please see one of my other pages on how to get Spring MVC, JSF, extJS or even standard Servlets/JSP’s running with the application discussed here.
You can drop in any “view technologies” once you have the below application running.
The application will retrieve a list of cities from a “mock” datamanager and display them in JSON format in a browser.
The page should take 10-15 minutes to implement.
Requirements
- Basic understanding of Spring
- Basic understanding of REST
- Java 5
- Maven 2
Procedure
Configure the project
Create a project using Maven archetype. Open up the command prompt and navigate to an empty directory.
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp
groupId: com.test
artifactId: springRestWeb
Answer the rest of the questions with defaults “Just hit the enter key”,
Cd to the project base folder.
cd springRestWeb
Since this is a web project maven does not create the java source foldder.
Create missing directory
mkdir -p src/main/java/com/test
Modify the project configuration file
Open up the pom.xml file and make sure it looks like the one below. In most cases you can just copy and paste the one below into your own.
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>com.test</groupId>
<artifactId>springRestWeb</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>springRestWeb</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>maven-restlet</id>
<name>Public online Restlet repository</name>
<url>http://maven.restlet.org</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20090211</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.restlet.jse</groupId>
<artifactId>org.restlet.ext.simple</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet.ext.spring</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet.ext.servlet</artifactId>
<version>2.0.5</version>
</dependency>
<!-- spring framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-servlet_2.5_spec</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.0.0.v20091005</version>
<configuration>
<scanIntervalSeconds>2</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
</build>
</project>
Web Application Configuration
The application uses the “org.restlet.ext.spring.SpringServerServlet” to respond to HTTP Requests. This servlet responds to URL’s that begin with “/app/*” and delegates the handling of the requests to the Resources based on the routing logic specified in the ApplicationContext.xml.
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>Archetype Created Web Application</display-name>
<!-- The following is used to initialize the spring framework when the web app starts. -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>stateApplication</servlet-name>
<servlet-class>
org.restlet.ext.spring.SpringServerServlet
</servlet-class>
<init-param>
<param-name>org.restlet.application</param-name>
<param-value>stateApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>stateApplication</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Spring Configuration
The stateApplication uses a router that further checks the URL and calls the proper “Resource” based on the mappings. The routing logic consists of a “url pattern” that will cause the specified “Resource” to be called upon to respond to the request.
Each map entry uses a “SpringFinder” to create an instance of the Resource. The instance of the resource can have any number of beans injected into it (based on spring configuration).
src/main/webapp/WEB-INF/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="stateDataManager" class="com.test.StateDataManager"/>
<bean id="stateApplication" class="org.restlet.Application" scope="singleton">
<property name="root">
<bean class="org.restlet.ext.spring.SpringRouter">
<property name="attachments">
<map>
<entry key="/state/{name}">
<bean class="org.restlet.ext.spring.SpringFinder">
<lookup-method name="create" bean="stateResource" />
</bean>
</entry>
</map>
</property>
</bean>
</property>
</bean>
<bean id="stateResource" class="com.test.StateResource" scope="prototype">
<property name="stateDataManager" ref="stateDataManager"/>
</bean>
</beans>
Create the Resource
The “Resource” is called upon to respond to requests. The “stateDataManager” member will be injected using the spring configuration seen above.
src/main/java/com/test/StateResource.java
package com.test;
import org.json.JSONArray;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
import org.springframework.beans.factory.annotation.Required;
public class StateResource extends ServerResource {
// the stateDataManager is injected using Spring
private StateDataManager stateDataManager;
// ServerResources are thread safe (spring "prototype")
private String state;
public StateResource() {
super();
}
@Override
protected void doInit() throws ResourceException {
super.doInit();
state = (String)getRequestAttributes().get("name");
}
@Override
protected Representation get() throws ResourceException {
JSONArray jsonArray = new JSONArray(stateDataManager.getCities(state));
StringBuffer out = new StringBuffer();
String cb = getQuery().getFirstValue("callback");
out.append(cb + "(");
out.append("{ \"totalCount\" : \"" + jsonArray.length() + "\",");
out.append("\"records\" : ");
out.append(jsonArray.toString());
out.append("}");
out.append(");");
return new StringRepresentation(out.toString());
}
@Required
public void setStateDataManager(StateDataManager stateDataManager) {
this.stateDataManager = stateDataManager;
}
public StateDataManager getStateDataManager() {
return stateDataManager;
}
}
Data Manager
This is the dummy data manager. In your situation you would replace the hard coding with a connection to a real database. I have many examples where Spring is used to connect to a mySQL database. Please search for “SimpleJDBC” on the top right corner.
src/main/java/com/test/StateDataManager.java
package com.test;
import java.util.ArrayList;
import java.util.List;
/**
* This is a test data manager that returns dummy data.
*/
public class StateDataManager {
public List<String> getCities(String state) {
// return the list of cities given a state
List<String> cityList = new ArrayList<String>();
if("TX".equals(state)) {
cityList.add("Houston");
cityList.add("San Antonio");
cityList.add("Dallas");
cityList.add("Austin");
cityList.add("El Paso");
cityList.add("Fort Worth");
} else if("NY".equals(state)) {
cityList.add("New York City");
cityList.add("Buffalo");
cityList.add("Rochester");
cityList.add("Yonkers");
cityList.add("Syracuse");
} else if("CA".equals(state)) {
cityList.add("Los Angeles");
cityList.add("San Diego");
cityList.add("San Jose");
cityList.add("San Francisco");
cityList.add("Fresno");
} else {
// don't add any cities
}
return cityList;
}
}
Test the Application
mvn clean compile jetty:run
Use your browser to navigate to the following URLs:
http://localhost:8080/app/state/CA
http://localhost:8080/app/state/NY
http://localhost:8080/app/state/TX
You should see something like this in your browser:
null({ "totalCount" : "5","records" : ["Los Angeles","San Diego","San Jose","San Francisco","Fresno"]});
The above format is designed to work with the extJS “Ext.data.ScriptTagProxy”. But you can use any number of view technologies to parse and display the data.
Creating a WAR file
If you want to run the application in another environment you can create a war file and drop it into a server of your choice. This method will also package up any runtime dependencies and put them in the /WEB-INF/lib folder of the WAR file.
mvn clean compile package
The war file should appear in the following location…
springRestWeb/target/springRestWeb-1.0-SNAPSHOT.war