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:
- http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/
- http://blog.springsource.com/2009/03/16/adding-an-atom-view-to-an-application-using-springs-rest-support/
- http://rwehner.wordpress.com/2010/06/09/2-ways-to-create-json-response-for-ajax-request-in-spring3/
- http://www.ibm.com/developerworks/web/library/wa-restful/
- http://forum.springframework.org/showthread.php?p=337206
- http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html
- use a ContentNegotiatingViewResolver
- use HttpMessageConverters in combination with the @ResponseBody annotation
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:
org.springframework.web.servlet.view.json.MappingJacksonJsonView
org.springframework.web.servlet.view.xml.MarshallingView
org.springframework.web.servlet.view.documentClass.AbstractPdfView
etc...
Using HttpMessageConverters
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 "15.3.2.5 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 [6] 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>
My choice
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.
Further issues
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:
http://www.devnexus.com/s/speakers
http://www.devnexus.com/s/speakers.html
http://www.devnexus.com/s/speakers.xml
http://www.devnexus.com/s/speakers.json
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:
http://www.gethifi.com/blog/browser-rest-http-accept-headers
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:
https://lists.webkit.org/pipermail/webkit-dev/2010-January/011188.html
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.
5 comments:
Gunnar,
Just for the records ;) Potential method for adding new message converters when using mvc:annotation-driven can be found at one of my posts http://vard-lokkur.blogspot.com/2010/11/spring-mvc-pitfall-overriding-default.html. I know, I wrote about WebBindingInitializer ;), but AnnotationMethodHandlerAdapter registered by mvc:annotation-driven tag has also getter/setter for messageConverters.
regards
PS: interesting article :)
Yes, ContentNegotiation based on the Browser-Accept-Header is absolutely evil. I went into the same trap because the spring docs write about it like the holy grail...
Just use separate URLs (e.g. via file extension) for separate content!
could you please post how you configured different view resolvers based on @ResponseBody+HttpMessageConverter
Suport the following formats
1. xml
2. json
3. html
4. jsp
5. image / video
Hi,
When i m using "InternalResourceViewResolver" & "BeanNameViewResolver" in my application then "InternalResourceViewResolver" is working.
Plz. tell solution, how i can use mulitple view resolver in my application.
E-mail ID: jagdeepsinghdhiman@live.com
Hi Gunnar,
I stumbled across your blog entry, when I searched for a solution to the problem, where I cannot have HttMessageConverter and a ViewResolver working together. But for my WebServices, I like to have a text/html view for all my WebServices.
So I just had a look at the AnnotationMethodHandlerAdapter which is responsible for delegating the WebRequests to a suitable method and for deciding for what to do with the call's result.
The solution is simple: You have the chance to translate your @ResponseBody to a ModelAndView by registering a custom ModelAndViewResolver with the adapter. In addition with a custom annotation (@ModelAndViewParams({@ModelAndViewParam(value = "myView", modelType = MyModelReturnedAsResponseBody.class}), It now creates a ModelAndView when certain preconditions are met, so the ViewResolver is used over the HttpMessageConverter.
I hope that helps everyone looking for a solution to the same problem.
Post a Comment