Quantcast
Viewing all articles
Browse latest Browse all 8

Building a RESTful Service Layer with JAX-RS, RESTEasy and Spring Framework

Just like many of you, we’ve been developing RESTful services for a while. We’ve used various frameworks in .NET and in Java, in large systems as well as on the server-side or our mobile apps. Unless there’s a good reason not to do so, REST is our preferred approach in building a service layer. Recently we’ve had a series of discussions about different approaches in building a RESTful service layer and I decided to outline one of the ways to do it using RESTEasy, Jackson, JSON and Spring Framework on the Java platform.

The following template project is Maven-based but I guess you can easily convert that to a Gradle project if you’d like to. At the end of it we are going to have an operation that’s just going to return a message wrapped in JSON and containing “Hello, World!”. I’m hoping to extend it by adding a MongoDB layer in a later post.

Domain

This is a very basic example. And our very simple Message class resides in its separate Maven module called domain. Here’s how Message class looks like:

package ie.decaresystems.domain;

public class Message {

  private String content;

  public Message(String aContent) {
    this.content = aContent;
  }

  @Override
  public String toString() {
    return content;
  }
}

Services

The next thing we’re going to do is to reason in terms of services and not necessarily in terms of Web, HTTP or REST. This is going to be our second module: a service layer that is free from protocol and transport constraints. And this module is going to have dependency to the domain module.

  ...
  <artifactId>services</artifactId>

  <dependencies>
    <dependency>
      <groupId>ie.decaresystems.rest</groupId>
      <artifactId>domain</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  ...

Our service interface is dead simple:

package ie.decaresystems.message;

import ie.decaresystems.domain.Message;

public interface Messenger {
  Message saySomething(String aType);
}

So is its implementation:

package ie.decaresystems.message;

import ie.decaresystems.domain.Message;

public class SimpleMessenger implements Messenger {
  @Override
  public Message saySomething(String aType) {
    if ("hello".equalsIgnoreCase(aType)) {
      return new Message("Hello, World!");
    } else {
      return new Message("What's up?");
    }
  }
}

RESTful Interface

At this point, we have two modules that we can unit-test without difficulty. Now, let’s expose the Messenger interface using a RESTful approach. Let’s create a module that’s going to hold our Web layer.

  ...
  <modules>
    <module>domain</module>
    <module>services</module>
    <module>web</module>
  </modules>
  ...

I used the maven-archetype-webapp archetype to create this module:

... -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp ...

This creates pretty much the following pom.xml (let’s take this opportunity to add the dependency to the services module):

  ...
  <parent>
    <artifactId>SpringRestEasyTemplate</artifactId>
    <groupId>ie.decaresystems.rest</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>web</artifactId>
  <packaging>war</packaging>
  <name>Webapp</name>

  <dependencies>
    <dependency>
      <groupId>ie.decaresystems.rest</groupId>
      <artifactId>services</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>template</finalName>
  </build>
  ...

Now, let’s create our RESTful service interface inside the web module.

package ie.decaresystems.rest;

public interface Communication {
}

As part of this simple project we’re going to create an operation accessible via the GET method and returning a greeting message wrapped in JSON. Let’s create a dependency to RESTEasy as our JAX-RS implementation.

  ...
  <properties>
    <resteasy.version>3.0.6.Final</resteasy.version>
  </properties>
  ...
    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-jaxrs</artifactId>
      <version>${resteasy.version}</version>
    </dependency>
  ...

The operation that returns a simple message is going to be invoked through the GET method. It is going to produce a JSON response. And its path is going to be /messages/{type} where type is the type of the message (which is added to simply showcase the scenario where a parameter is passed in):

package ie.decaresystems.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public interface Communication {
  @GET
  @Path("/messages/{type}")
  @Produces(MediaType.APPLICATION_JSON)
  Response saySomething(@PathParam("type") String aType);
}

The implementation of the Communication interface is called Communicator. It’s going to be accessed using the path /communicator (that is in addition to the RESTEasy servlet prefix and Web app context, if any).

package ie.decaresystems.rest;

import ie.decaresystems.domain.Message;
import ie.decaresystems.message.Messenger;
import ie.decaresystems.message.SimpleMessenger;

import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path("/communicator")
public class Communicator implements Communication {
  @Override
  public Response saySomething(String aType) {
    Messenger messenger = new SimpleMessenger();
    Message message = messenger.saySomething(aType);
    return Response.ok(message).build();
  }
}

The saySomething() method instantiates a SimpleMessenger object and calls its saySomething() method by passing the type that it receives from the REST client. Then, naively assuming that everything goes fine (because there’s no error handling etc.) it includes the returned Message object as the entity of the response and returns a 200 OK response.

The code above takes advantage of the static ok() method of the Response class. That line could also have been written, among others, using the following:

  ...
  return Response.status(Response.Status.OK).entity(message).build();
  ...

Web Container Configuration

Now that our RESTful interface is ready, let’s configure our Web Container to run this example. Have a look at the following web.xml file:

  ...
  <listener>
    <listener-class>
      org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
    </listener-class>
  </listener>

  <context-param>
    <param-name>resteasy.scan</param-name>
    <param-value>true</param-value>
  </context-param>

  <context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/api</param-value>
  </context-param>

  <servlet>
    <servlet-name>resteasy-servlet</servlet-name>
    <servlet-class>
      org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
    </servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>resteasy-servlet</servlet-name>
    <url-pattern>/api/*</url-pattern>
  </servlet-mapping>
  ...

We’re using org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap to bootstrap RESTEasy. It is a ServletContextListener that creates a registry and configures a ProviderFactory. This is not the only way to bootstrap RESTEasy but I find it very easy this way.

You probably guessed it already: resteasy.scan tells RESTEasy to automatically scan WEB-INF/lib and WEB-INF/classes directories for classes annotated with JAX-RS annotations such as @GET, @POST, @Path, etc.

We, then, configure the dispatcher servlet that’s going to service the requests sent to our RESTful services. Here we picked /api as the mapping however you’re free to choose whatever suits your needs.

When we build and deploy our WAR file to our Web Container we should see a similar message to the following line in our server console:

...
PM org.jboss.resteasy.plugins.server.servlet.ConfigurationBootstrap 
INFO: Adding scanned resource: ie.decaresystems.rest.Communicator
...

This is clearly telling us that our Communicator interface is added to RESTEasy’s register. Because we have a single operation that can be invoked via a GET request you can use your browser to do it. In my case, I’m running my Tomcat on http://localhost:8080 and I deployed our application under the context called testapp. Therefore my URL is: http://localhost:8080/testapp/api/communicator/messages/hello.

It’s not necessary at this point however I would highly recommend you getting familiar with a tool that will quickly query your services. And I’m not only talking about GET requests but all the other possible methods. I’m a big fan of cURL. If you’re using cURL, you can use the following command:

curl -i -H "Accept: application/json" -X GET "http://localhost:8080/testapp/api/communicator/messages/hello"

With the above command you’ll be able to see the HTTP headers as well as the message returned by the server.

Alternatively if you are using Chrome, you can use Postman.

This is certainly not an exhaustive list of REST client tools that you can use. I used others in the past, such as SOAP UI, however I’m pretty happy with cURL and Postman. Please let us know if you are using another tool.

When we hit that URL, cURL returns an error:

HTTP/1.1 500 Internal Server Error
Server: Apache-Coyote/1.1
Content-Type: text/html
Content-Length: 125
Date: Tue, 11 Feb 2014 05:31:07 GMT
Connection: close

Could not find MessageBodyWriter for response object of type: ie.decaresystems.domain.Message of media type: application/json

From the headers, we can see that this is a server-side error. The message tells us that RESTEasy cannot turn our domain object (Message) into JSON. Before we resolve this particular problem let’s see what happens if we hardcode a JSON response. Replace the body of the Communicator.saySomething() method with the following single line:

  return Response.ok("{type: \"" + aType + "\"}").build();

After refreshing our app we get the following response:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Content-Length: 15
Date: Tue, 11 Feb 2014 05:45:00 GMT

{type: "hello"}

Perfect! We get back a 200 OK alongside the JSON message that we hardcoded. Now, let’s put back the Communicator.saySomething() method the way it was previously and let’s resolve the MessageBodyWriter problem that we encountered a few minutes ago.

Jackson

We are going to use Jackson as our JSON processor to marshall/unmarshall our objects. Jackson is a powerful and lightweight library. Ideally, once we configure it, it shouldn’t get in our way.

Let’s open our web module’s pom.xml and Jackson-enable our module:

    ...
    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-jackson-provider</artifactId>
      <version>${resteasy.version}</version>
    </dependency>
    ...

When we test our service we get back a 400 Bad Request:

No serializer found for class ie.decaresystems.domain.Message and no properties discovered to create BeanSerializer...

This is happening because Jackson has no visibility on our Message class’s content field. We have a few options to remedy to that problem:

Getters & Setters

If we own the code that needs to be marshalled/unmarshalled then we can add getters and setters to the selected fields of our classes:

  ...
  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }
  ...

And we have the JSON representation of our Message object:

> curl -i -H "Accept: application/json" -X GET "http://localhost:8080/testapp/api/communicator/messages/hello"

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 11 Feb 2014 22:15:55 GMT

{"content":"Hello, World!"}

Note that the Content-Type is correctly set to application/json. And if we pass in anything else than hello then the response changes to:

> curl -i -H "Accept: application/json" -X GET "http://localhost:8080/testapp/api/communicator/messages/bonjour"

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 11 Feb 2014 22:20:07 GMT

{"content":"What's up?"}

If you try these on Postman (the in-browser tool that I mentioned above) then you get the same thing on a nicer UI (which is definitely more practical in eyeballing the output when bigger data sets returned):

Image may be NSFW.
Clik here to view.
Postman - Beautified JSON response

Postman – Beautified JSON response

Empty Beans

The previous error was generated because our Message class didn’t have any visible fields. If we don’t want to encounter this error during empty-bean processing then we can disable the SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS property.

Field Visibility

We can also modify the visibility of the fields. By default Jackson will look for public getters and setters or public fields. However the following call will allow a more global detection of the fields regardless of their visibility:

  ...
  ObjectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
  ...

Service Injection using Spring Framework

Let’s add more flexibility to our application by loosening the coupling between our modules. Ideally we only want dependencies based on interfaces and not concrete classes.

@Path("/communicator")
public class Communicator implements Communication {
  @Override
  public Response saySomething(String aType) {
    Messenger messenger = new SimpleMessenger();
    Message message = messenger.saySomething(aType);
    return Response.ok(message).build();
  }
}

Above, the highlighted line contains the instantiation of our SimpleMessenger service. I’d like to remove this and inject our Messenger implementation using Spring.

Let’s first make the necessary modifications to Communicator.java:

  ...
  private Messenger messenger;

  @Override
  public Response saySomething(String aType) {
    Message message = messenger.saySomething(aType);
    return Response.ok(message).build();
  }

  public void setMessenger(Messenger messenger) {
    this.messenger = messenger;
  }
  ...

We created a private field of type Messenger and a public setter to inject an implementation. This way we remove our dependency on the concrete SimpleMessenger class (new SimpleMessenger() has disappeared).

Because we’re going to use Spring Framework to inject the required dependency, we need to find a way to declare Communicator as a Spring-managed bean. There are many ways to do this. Here we’re going to use Spring’s @Service annotation.

@Path("/communicator")
@Service
public class Communicator implements Communication {
  ...

We create a XML-based Spring configuration file. Following the convention and also ensuring auto-discovery, we name the configuration file applicationContext.xml and we place it under WEB-INF. In order to enable component auto-detection we use component-scan. We also declare a concrete Messenger instance as a Spring bean.

  ... 
  <context:component-scan base-package="ie.decaresystems.rest"/>

  <bean id="messenger" class="ie.decaresystems.message.SimpleMessenger" />
  ...

When it comes to injecting a bean into another there are many options. We are going to keep the configuration file lighter and take advantage of the @Resource annotation in conjunction with the bean id that we used above:

  ...
  @Resource(name = "messenger")
  private Messenger messenger;
  ...

Because of Spring integration, our web.xml gets its share of modification as well. RESTEasy comes with its own Spring ContextLoaderListener not-so-surprisingly called org.jboss.resteasy.plugins.spring.SpringContextLoaderListener and we’re going to use it. We’re also going to turn off RESTEasy’s scanning (delete the entry completely), which was previously set to true.

  ...
  <listener>
    <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
  </listener>
  ...

Last but not least, let’s add the new dependencies to the Maven configuration file of our Web module:

  ...
  <properties>
    <resteasy.version>3.0.6.Final</resteasy.version>
    <spring.version>3.2.8.RELEASE</spring.version>
  </properties>
  ...
  <dependencies>
    ...
    <!--RESTEasy - Spring-->
    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-spring</artifactId>
      <version>${resteasy.version}</version>
    </dependency>

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

When we build and run our little project we see the expected responses:

> curl -i -H "Accept: application/json" -X GET "http://localhost:8080/testapp/api/communicator/messages/hello"

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 13 Feb 2014 22:32:54 GMT

{"content":"Hello, World!"}

> curl -i -H "Accept: application/json" -X GET "http://localhost:8080/testapp/api/communicator/messages/bonjour"

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 13 Feb 2014 22:35:27 GMT

{"content":"What's up?"}

Spring Framework 4.x

In the previous section we used Spring Framework 3.2. In a future blog post I’m going to describe the modifications necessary to make this solution work with Spring Framework 4.x.

Source Code

You can find this project’s source code on its GitHub Repository.


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 8

Trending Articles