RESTful web services are generally hyped these days – and for many good reasons: among others, the fact that they are easily consumed by almost any kind of client – browsers, mobile apps, desktop apps etc.
One technology stack for building restful services in a Java environment could comprise Jersey, Gson and Guice (nice alliteration, by the way…). Without prior knowledge to any of these technologies, me and my team managed to successfully establish a RESTful web service consumed by for example this website.
I will briefly introduce these 3 frameworks:
Jersey and JAX-RS
Jersey is one of several implementations of the JAX-RS interface – the Java API for RESTful web services.
Jersey provides a servlet that analyses an incoming HTTP request by scanning underlying classes for RESTful resources, and selecting the correct class and method to respond to this request. The RESTful resources are defined by decorating classes and methods with the appropriate JAX-RS annotations.
If you for example have a UserService
class that you want to expose through a RESTful API, you can wrap it in a UserWebService
class and decorate this class and its methods with JAX-RS annotations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Path("user") public class UserWebService { private final UserService _userService; @Inject public UserWebService(UserService userService) { if (userService == null) { throw new NullPointerException("userService"); } _userService = userService; } @GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) public Response GetUserList(@Context HttpHeaders httpHeaders) { try { return Response.ok(_userService.getAll(), MediaType.APPLICATION_JSON).build(); } } catch (Exception e) { throw new InternalServerErrorException(e.getMessage()); } } ... } |
The @Path
annotation specifies on which (relative) URL path this method will be invoked. The @Get
annotation specifies that the http method GET
has to be used and the @Produces
annotation declares the format of the response.
So, the following http-request:
GET http://localhost:8080/myservice/api/user/list
will invoke the GetUserList()
method, which basically is a pass-through to the UserService.getAll()
method, and return a response with a list of users in JSON format.
JSON support using Gson
One of the decisions you have to make when establishing a RESTful service is which representation formats (media types) to support. Very often JSON will be the obvious choice – especially if the services are to be consumed by browser-based clients which typically use JavaScript.
In order to produce and consume JSON you need a serialization mechanism that turns a Java object into a JSON document and vice versa (under-the-hood the representation bodies will very often be POJO objects). Our choice was to use Google Gson for this purpose.
You simply need to implement the two interfaces javax.ws.rs.ext.MessageBodyWriter
and javax.ws.rs.ext.MessageBodyReader
, and decorate the implementing classes with the JAX-RS @Provider
annotation. Here is the writer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Provider @Produces(MediaType.APPLICATION_JSON) @Singleton public class GsonWriter<T> implements MessageBodyWriter<T> { @Override public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { Gson g = new Gson(); httpHeaders.get("Content-Type").add("charset=UTF-8"); entityStream.write(g.toJson(t).getBytes("UTF-8")); } @Override public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } } |
And here is the reader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
@Provider @Consumes(MediaType.APPLICATION_JSON) @Singleton public class GsonReader<T> implements MessageBodyReader<T> { @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] antns, MediaType mt) { return true; } @Override public T readFrom(Class<T> type, Type genericType, Annotation[] antns, MediaType mt, MultivaluedMap<String, String> mm, InputStream in) throws IOException, WebApplicationException { Gson g = new Gson(); return g.fromJson(_convertStreamToString(in), type); } private String _convertStreamToString(InputStream inputStream) throws IOException { if (inputStream != null) { Writer writer = new StringWriter(); char[] buffer = new char[1024]; try { Reader reader = new BufferedReader( new InputStreamReader(inputStream, "UTF-8")); int n; while ((n = reader.read(buffer)) != -1) { writer.write(buffer, 0, n); } } finally { inputStream.close(); } return writer.toString(); } else { return ""; } } } |
Guice as DI container
In a previous post I showed how to use Guice as a DI container in a Jersey application. So, what is left now is to bind the Gson writer and reader – ass well as other types, such as the RESTful resource classes – in the Guice injector:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class GuiceServletConfig extends GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.createInjector(new JerseyServletModule() { @Override protected void configureServlets() { bind(GsonWriter.class); bind(GsonReader.class); ... bind(new TypeLiteral<IRepository<User>>() {}) .to(UserRepository.class).in(Singleton.class); bind(UserWebService.class); ... serve("/api/*").with(GuiceContainer.class); } }); } } |
To summarize, a well-proven technology stack for implementing a RESTful web service in Java comprises Jersey as the REST framework, Google Guice as the DI container to support dependency Injection and Google Gson for JSON serialization and de-serialization of the representation body objects. The service can be deployed on for example a Glassfish server.