I\'m trying to make Jersey supports GSON and for this I have read that I need to implement a Custom MessageBodyWriter and MessageBo
Just means the set of exposed method by the interfaces. If we implement the interface, we must implement the set of contract methods, for which the framework will use to make use of our implementations
MessageBodyWriter
- Contract for a provider that supports the conversion of a Java type to a stream - From our JAX-RS resource methods, we return either a Response
with an entity body (which is a Java Object) or a Java object. The message body writer is responsible for converting this Java object to the response's output stream. For example (just for play imagine we don't already have support for JAXB marshalling)
@Override
public void writeTo(Customer c, Class<Customer> type,
Type genericType, Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String,Object> httpHeaders,
OutputStream entityStream) {
JAXBContext context = JAXBContext.newInstance(type);
Marshaller marsaller = context.createMarshaller();
marshaller.marshal(c, entityStream);
}
You can see that I create the Marshaller
, which marshals the Customer
object to the provided (by the framework) OutputStream
MessageBodyReader
- Contract for a provider that supports the conversion of a stream to a Java type - Same deal as the writer, but this time the process is reversed. Before a Java type is passed to our JAX-RS resource methods, it needs to be converted. For example
@Override
public Customer readFrom(Class<T> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String,String> httpHeaders,
InputStream entityStream) throws IOException,
WebApplicationException {
JAXBContext context = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
return (Customer)unarshaller.unmarshal(entityStream);
}
To add a
MessageBodyWriter
implementation, annotate the implementation class with@Provider
. AMessageBodyWriter
implementation may be annotated withProduces
to restrict the media types for which it will be considered suitable
JAX-RS has the concept of Providers. You can just think of it another word for Component. When we annotate our JAX-RS implemented classes with @Provider
, it becomes eligable to be component, managed by the framework.
With MessageBodyWriters/MessageBodyReaders
, there is a specific algorithm that goes into determining which writer/reader will be used for each request/response. Basically what happens is that JAX-RS calculates a list or writers, based on the @Produces
annotations matches. For example if our resource method or resource class is annotated with @Produces(MediaType.APPLICATION_XML)
and our provider (writer) is also annotated with this, our writer will be put into this list. Then these providers are sorted by some algorithm. Finally the isWritable
method is called on each writer until true
is returned by one of them. That is the writer that will be used. For example
@Provider
@Produces(MediaType.APPLICATION_XML)
public class MyJAXBMessageBodyWriter
implements MessaheBodyWriter<Customer> {
@Override
public boolean isWriteable(Class<Customer> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == Customer.class;
}
}
@GET
@Produces(MediaType.APPLICATION_XML)
public Response getCustomer() {
Customer customer = customerService.getCustomer();
return Response.ok(customer).build();
}
These two will be matched, and our writer will be used to convert our Customer
object
A
MessageBodyReader
implementation may be annotated withConsumes
to restrict the media types for which it will be considered suitable.Providers
implementingMessageBodyReader
contract must be either programmatically registered in a JAX-RS runtime or must be annotated with@Provider
annotation to be automatically discovered by the JAX-RS runtime during a provider scanning phase.
The @Consumes
annotation, same deal as the writer the @Produces
, just reversed. Same matching algorithm. Also the @Provider
annotation, same deal. For example
@Provider
@Consumes(MediaType.APPLICATION_XML)
public class MyJAXBMessageBodyReader
implements MessageBodyReader<Customer> {
@Override
public boolean isReadable(Class<Customer> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == Customer.class
}
}
@POST
@Consumed(MediaType.APPLICATION_XML)
public Response createCustomer(Customer customer) {
customerService.save(customer);
return Response.created(newCustomerUri).build();
}
The reader will be matched to our method and convert the input stream into a Customer
object and pass it to our method.
As far as the last words "scanning phase", this is just the JAX_RS implementation scanning our packages looking for components to manage. This happens at startup, based on our configurations (e.g. what packages should be scanned)
MessageBodyWriter
has a another method getSize
. We can just return -1
if we don't know the size, and the framework will determine the size for us.
In case you want to programmatically register a MessageBodyReader
or MessageBodyWriter
, use the register
methods on your Client
or ClientBuilder
(or any Configurable
instance).