Search This Blog

Monday, 9 September 2013

Different Message Formats in Spring MVC

In an earlier post we saw how Spring depended on MessageConverters to convert the request stream to java object and also the java objects to appropriate response formats.
As we had defined a Jackson converter, our controller's return objects were converted by the DispatcherServlet to JSON.
Similarly where the method parameters were annotated with Requestbody, DispatcherServlet ensured that the message converter read the request and converted it to the Java object of the parameter's type.
But what if we build a service that must be capable of returning resource in various formats ?
For example I would like my resource to expose both JSON and XML representation of the data. This gives alternatives to the client. But I also need my same resource (or more specifically - same URL) to support these varied formats.
The first step would be to set the message converts needed:
For JSON like before:
<bean id="jsonMessageConverter"
      class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
For XML, Spring provides the MarshallingHttpMessageConverter:
<bean id="xmlConverter"
      class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
      <constructor-arg ref="jaxbMarshaller" />
   </bean>
The class uses JAXB internally for the marshaling and unmarshaling process. So I had to add a Jaxb2Marshaller:
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
      <property name="classesToBeBound">
         <list>
            <value>com.test.controller.User</value>
         </list>
      </property>
   </bean>
The class is from Spring's oxm jar. (Also make sure to annotate the bound classes with JAXB's XmlRootElement annotation. Else Spring throws a weird 406 error)
The third format I included was ATOM:
<bean id="atomConverter"
      class="org.springframework.http.converter.feed.AtomFeedHttpMessageConverter" />
For Atom to work I also had to add the sun.syndication-1.0.0.jar

Now that we have created the message converters, we need to make the dispatcher servlet aware of the same:
<bean
      class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
      <property name="messageConverters">
         <list>
            <ref bean="jsonMessageConverter" />
            <ref bean="xmlMessageConverter" />
            <ref bean="atomConverter" />
         </list>
      </property>
   </bean>
As seen here the "messageConverters" property is capable of accepting a list of converters.
This completes the configuration on the server. Thus adding new data formats to the Resource has minimal code impacts.
Now consider the get call: http://localhost:8080/SampleRest/api/user/1
This will cause server to return the user with id 1. But there remains an important question.
How does the Server know in what format to return the user record?
This is where the accept header comes in. All Requests made must include the expected format in the accept header. Correspondingly all message converts are associated with a particular media type.
For e.g. if we look at the MappingJacksonHttpMessageConverter
public MappingJackson2HttpMessageConverter() {
      super(new MediaType("application", "json", DEFAULT_CHARSET));
   }
Here the messageConverter will work for media content of type "application/json".
Similarly for MarshallingHttpMessageConverter we have "text/xml" and "application/xml".
For AtomFeedHttpMessageConverter it is "application/atom+xml".
We are not tied to using these types only. We can provide our own values if needed:
<bean id="jsonConverter"
      class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
      <property name="supportedMediaTypes" value="application/json" />
   </bean>
The client must provide one of these values in its Accept Header. This enables the DispatcherServlet to process the request and generate the response correctly.

3 comments: