Recently I posted a couple of articles on SOAP web services. Here, I discuss the Representational State Transfer (REST) architecture as an alternative to SOAP and WSDL. REST has gained widespread acceptance across the web as being an easier-to-use, resource-oriented model to expose your services.
Although there is room for overlap, SOAP is mostly useful for invoking behavior (as you might have already noticed on one of my previous posts) while REST is good for managing information. Probably the most interesting aspect of the REST vs. SOAP debate is the security topic, while SOAP sends remote procedure calls (RPC) through standard HTTP ports, REST calls go over HTTP or HTTPS. In general, sensitive data should not be sent as parameters in URIs, also large amounts of data such as in purchase orders can become cumbersome or even out of bounds within a URI. I will not engage in a SOAP vs REST discussion here (Its way beyond the scope of this article), instead I will go through the basics of creating a REST web service API using SUN's JAX-RS implementation for Java: Jersey. If you feel like doing some reading on this topic, I recommend the article on SOAP or REST? it's about your priorities! and also Giving SOAP a REST.
First of all, lets all be clear that Jersey is not the only JAX-RS implementation out there. Other implementations by different vendors include: RESTEasy (from JBoss), CXF (from Apache), Restlet, etc. You can find a detailed discussion on this topic at the SOA Community Blog
1. Background (in a nutshell):
JAX-RS is an annotation-based API for implementing RESTful web services. Jersey is the JAX-RS reference implementation from Sun. Because REST uses HTTP as the communication protocol, the REST style is constrained to a stateless client/server architecture.
2. Requirements:
• Java 5 or above (because of its heavy reliance on annotations).
• Jersey JARs. Download Jersey's archives from the Jersey Project site and add them to your web project library. Here, I'm using
asm-3.1.jar, jersey-core.jar, jersey-server.jar, jsr-311-api-1.0.jar
3. Environment Set-Up:
Declare Jersey's framework Servlet inside the
web.xml
<servlet>
<servlet-name>JerseyController</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JerseyController</servlet-name >
<url-pattern>/*</url-pattern>
</servlet-mapping>
Jersey uses a Servlet called Grizzly Servlet, and the servlet
(com.sun.jersey.spi.container.servlet.ServletContainer)
handles the requests to Grizzly. 4. Implementation:
In its most simplest form, Jersey requires no more than a couple of annotations to build a trivial service:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path ("/printname")
public class PrintNameResource {
@GET
@Produces ("text/plain")
public String printName() {
return "Hi Tulio Domingos";
}
}
@Path
Indicates what URI path is mapped to the resource. For example, you would access this class using the following URL: http://localhost:8080/<context>/printname
@GET
JAX-RS provides a clear mapping between the HTTP protocol and the URIs through well-defined classes and interfaces. @GET, @POST, @DELETE, etc.
The type of HTTP method request dictates which request method designator is called. For example, if the request is from a HTTP GET request (above), the service automatically invokes a method annotated with @GET and provides the response.
@Produces
This annotation specifies the MIME type for the response (a representation that can be produced by a resource and sent back to the client)
Above, I illustrate the use of 3 basic JAX-RS annotations. In case you feel the need to have two or more methods handling HTTP GET requests, then make sure you provide a path (@Path annotation) for each method so they don't clash.
The above sample code, as you might have guessed, is useless as I have hard coded the response to client. Consider a scenario where the server constructs the response based on a URI query parameter 'name'.
i.e. http://localhost:8080/<context>/printname?name=tulio
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
@Path ("/printname")
public class PrintNameResource {
@GET
@Produces ("text/plain")
public String printName(@QueryParam("name") String name) {
return "Hi " + name;
}
}
Alternatively, you can make use of relative URI paths. As discussed earlier, you identify a root resource class using the @Path annotation. The value of the annotation can have a relative URI path template between curly braces {,}, with the deployment context providing the reference to the base URI. Note as well you use it in conjunction with the @PathParam as opposed to the @QueryParam (above)In this case, the URI would look more like:
i.e. http://localhost:8080/<context>/printname/tulio
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path ("/printname/{name}")
public class PrintNameResource {
@GET
@Produces ("text/plain")
public String printName(@PathParam("name") String name) {
return "Hi " + name;
}
}
Change the @Produces annotations to specify a MIME type for the response that suits your needs (HTML, XML, JSON, Image, etc). Resource classes can produce or consume any type of MIME. Use annotations to specify the MIME media type for both request and response on a resource class or resource method.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
@Path ("/printname")
public class PrintNameResource {
@GET
@Produces("text/xml")
public String printName(@QueryParam("name") String name) {
return "<msg>Hi " + name + "</msg>";
}
}
One of the coolest features of JAX-RS/Jersey is the automatic creation of XML and JSON via JAXB annotations! That's right, no further coding is required, below is an example of how easily a POJO can be turned into a REST service, simply by adding a JAXB annotation:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
@Path ("/message")
public class PrintNameResource {
@GET
@Produces("text/xml")
public Message getMessage(@QueryParam("name") String name) {
Message message = new Message();
message.setContent("Hi " + name);
return message;
}
}
And the message bean would look something like:
@XmlRootElement
public class Message{
private String content;
public String getContent(){
reuturn content;
}
public void setContent(String c){
this.content = c;
}
}
JAXB is often associated with web services. With JAXB you can generate XML from either an XSD or your own POJO, also you're free to manually marshal them may you find it more convenient.
5. HttpSessions in Jersey:
Feel the need to access the session object in Jersey? Below is an example on just how you can achieve this:
@GET
@Path("/getsession")
@Produces(MediaType.APPLICATION_JSON)
@Consumes("application/x-www-form-urlencoded")
public String getSession(@Context HttpServletRequest req){
String jsessionid = req.getSession().getId();
logger.info("JSESSIONID is: " + jsessionid);
return jsessionid;
}
Alternatively, you can annotate your resource class with the
@PerSession
annotation so that a new instance of the resource class gets created for each session. This instance will remain servicing the client for as long as the session lives. Refer to its javadoc for more information.
6. Jersey's support for JSON:
Jersey added support for JSON by including the library
jettison-1.0.jar
in its bundle. Add this library to the build path of your project and change the @Produces annotation to something like @Produces("application/json"). Make sure to use it in conjunction with JAXB annotation (above). Note: If you need to append bytes to the response, whether its in JSON, XML, or any other text format, then use some form of encoding such as BASE64. Byte values would turn into illegal characters when converted to string. The same happens in a SOAP message.
According to the JSON documentation, JSON supports any UNICODE character except (1) control characters, (2) \ and (3) "
7. Jersey's support for Spring:
Follow the steps below in order to integrate Spring with Jersey:
• Download and add the Spring JARs to the build path of your project
• Include Jersey's Spring library:
jersey-spring.jar
• Also add
antlr
version 3: antlr-3.0.jar
. This is important! If you have the wrong antlr
library, you might face errors of type: Context initialization failed. java.lang.NoSuchFieldError: ruleMemo
• Configure Jersey's Spring servlet in your
web.xml
along with its respective init-parameters: com.sun.jersey.config.property.resourceConfigClass
and com.sun.jersey.api.core.PackagesResourceConfig
<servlet>
<servlet-name>Jersey Spring</servlet-name>
<servlet-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</servlet-class>
<init-param>
<param-name>
com.sun.jersey.config.property.resourceConfigClass
</param-name>
<param-value>
com.sun.jersey.api.core.PackagesResourceConfig
</param-value>
</init-param>
<init-param>
<param-name>
com.sun.jersey.config.property.packages
</param-name>
<param-value>
com.tcm.appstore.ws.resources
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Spring</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
• Create the Spring application context file (
spring-context.xml
) as you would normally in Spring and specify it in the web.xml
via a context parameter:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-context.xml</param-value>
</context-param>
• Last but not least, register the Spring context loader listener also in the
web.xml
:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
• An interesting feature of Jersey Spring, is its dependency injection mechanism through the use of annotations. This feature enables the injection of a Spring bean into you application using the
@Inject
annotation. Below is an example of such feature (assume the message bean is declared in the spring-context.xml
):
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import com.sun.jersey.spi.inject.Inject;
@Path ("/message")
public class PrintNameResource {
@Inject
Message message;
@GET
@Produces("text/xml")
public Message getMessage() {
return message;
}
}
Please bear in mind that all code samples outlined in this article were not tested, as a result, syntax mistakes might have occurred at some point down the line. Any form of feedback is welcome.
6. Further Reading:
• http://www.javaworld.com/javaworld/jw-10-2008/jw-10-rest-series-1.html
• http://www.devx.com/Java/Article/42873/1954
Nice tutorial!
ReplyDelete