REST with Spring - ContentNegotiatingViewResolver vs. HttpMessageConverter+ResponseBody Annotation
Disclaimer: This blog posting reflects experiences while learning to implement RESTful services. Thus, certain elements of my blog posting may turn our to be not correct. Therefore, if you encounter errors, please let me know and I will post corrections as soon as possible. Please proceed with caution...
As I posted in my previous blog "Making DevNexus.com more Restful", I am in the process of making more of the data from DevNexus.com consumable by other services, by exposing JSon and XML based endpoints. The website/application is implemented using Spring MVC 3.0 in the view layer using Spring's REST support.
Here are a few good reads that I came across that provide some helpful information:
As it turns out, there are 2 ways of implementing REST endpoints:
- use a ContentNegotiatingViewResolver
- use HttpMessageConverters in combination with the @ResponseBody annotation
Using a ContentNegotiatingViewResolver
When you use a ContentNegotiatingViewResolver your web controllers return ModelAndViews or view names and the ContentNegotiatingViewResolver will, based on various criteria, choose the right data representation strategy.
The highest priority hereby has the file extension which is used if available in the request. Next, the ViewResolver will look for a (definable) request parameter that identifies the view. If that does not help, the ViewResolver uses the Java Activation Framework to determine the Content-Type. If all fails, use the the HTTP Accept header. Of course the steps can be individually disabled.
A key takeaway though is, that your controllers will return a single ModelAndView/Viewname that will resolve into a specific view such as:
However, this may feel a little unnatural for certain data representations such as XML (using Jaxb annotations) or Json (using Jackson), where a dedicated view may not be necessary. Luckily, you can configure the ContentNegotiatingViewResolver to use default views which kind of solves the issue.
This is where HttpMessageConverters potentially could help. Whenever you use the @ResponseBody annotation you will be using a HttpMessageConverter (See also the Spring reference documentation, chapter "188.8.131.52 Mapping the request body with the @RequestBody annotation" and "18.3.2 HTTP Message Conversion").
What this means is, that instead of returning a ModelAndView or view name, you will actually return data, e.g. a Collection of Objects.
If you use the Spring Context <mvc:annotation-driven/> then support for XML and JSON marshalling is activated by default, provided the respective class libraries (Jaxb and/or Jackson) are present in your class-path (See the Spring documentation for details at chapter "15.12.1 mvc:annotation-driven")
Here is the interesting problem, if you use <mvc:annotation-driven/> you can set additional conversion services but you cannot set additional HttpMessageConverters. Consequently, if you want to do that then you have to use explicit bean declarations and remove the <mvc:annotation-driven/> tag from your context. This was something not too well covered in article  http://www.ibm.com/developerworks/web/library/wa-restful/
Ultimately, I am using the following bean declaration instead of the <mvc:annotation-driven/> tag:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="webBindingInitializer"> <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService"/> <property name="validator" ref="validator"/> </bean> </property> <property name="messageConverters"> <list> <ref bean="jsonConverter" /> <ref bean="marshallingConverter" /> <ref bean="atomConverter" /> </list> </property> </bean>
This was a bit tricky. Both approaches are somewhat overlapping and I wish the documentation would give you better guidance on which approach to use under which cicumstances. Certainly the ContentNegotiatingViewResolver option seems to be the better documented solution. On the other side, you don't need to configure explicit views when using HttpMessageConverters and the ResponseBody annotation and therefore, that setup looks a bit more streamlined.
One issue I was running into was, that for my application I can return both pure data views (Json, XML) and also Html/Jsp responses. Somehow I was not able to configure my controller easily+cleanly, to respond to the same Url with multiple controller methods (one using @ResponseBody and the other returning a JSP view)
Secondly, I wanted to also support file extensions to use the correct view or converter. As it turns out though, HttpMessageConverters don't support that - Although there was an example somewhere for using request parameters. But that approach would require me creating additional custom classes...
Anyway, I went ahead chose the ContentNegotiatingViewResolver approach to implement my Restful services.
Once I made my decision, everything seemed to go smoothly. I got my services implemented quickly and they worked perfectly in Firefox. Here is an example of the intended Url structure:
With that I thought to have covered all bases: Support file extensions, but also allow clients to connect to http://www.devnexus.com/s/speakers and retrieve all data representations using the respective Http Accept header.
I deployed the app into production, but then the next day at the monthly Atlanta Users Group meeting - people informed me that the DevNexus site were down. That was odd, as I had accessed the site just minutes prior to the meeting.
Well, as it turned out, Google Chrome, Safari and Internet Explorer transmit a wild mixture of Http Accept headers. Consequently, when users accessed http://www.devnexus.com/s/index then the server would try to return an Xml view because Chrome and Safari requested Xml data rather than Html data.
For more details on Accept headers, please see the following fascinating:
What also threw me off, was that quite a few sources, incl. the Spring documentation imply that using the Http Accept header might be a actually a viable way of determining the correct view to return to clients.
To illustrate the chaos -Here is an interesting posting from the webkit mailing list:
Ultimately, I got my servlet context configured in a way I think works best for me, though. The extension-less Url will always return Html now and for other data representations the file extension is mandatory. I also configured the ContentNegotiatingViewResolver to ignore Http Accept header by setting:
<property name="ignoreAcceptHeader" value="true" />
Many thanks to this blog posting by Rick Herrick. Thus, now my servlet-context.xml file contains:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="order" value="1" /> <property name="ignoreAcceptHeader" value="true" /> <property name="mediaTypes"> <map> <entry key="xml" value="application/xml"/> <entry key="json" value="application/json"/> </map> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.xml.MarshallingView"> <property name="marshaller" ref="jaxbMarshaller"/> </bean> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"> <property name="objectMapper" ref="jaxbJacksonObjectMapper"/> </bean> </list> </property> </bean> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order" value="2"/> </bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> <property name="order" value="3"/> </bean>
That was a rather long day to get things working as intended but a good learning experience nonetheless. There is certainly something intriguing about having clean Urls.