HttpMessageConverter Vs ContentNegotiatingViewResolver
Joined: Dec 24, 2006
I am confused about HttpMessageConverter and ContentNegotiatingViewResolver. Both does the same work or they are different? if we've an object that we wanna represent in xml format we can do it by using ContentNegotiatingViewResolver, same thing can be done using HttpMessageConverter. Then what is the difference?
I am really confused. Please let me know if my question is not correct.
Joined: Dec 24, 2006
I would be grateful if someone can describe with examples.
a View resolver does one thing. Resolve to a view, whether that might be a .jsp file, or some other rendered view. But it could also be a rendered view of JSON or XML in the body of the request or response. So a resolver might look in the request and see that the user wants JSON back, that is what a view resolver like ContentNegotiationViewResolver is doing, but the conversion from your Java domain objects to that content type is done through a HttpMessageConverter in Spring MVC. But with that it will basically delegate to maybe JAXB for xml mapping and rendering, or Apache Jackson for JSON.
Thanks for your reply. Still I've some doubts, please refer the code below which is related to it.
Everything is fine for above code. I got json view as well as xml view for Fruit resource. But as shown I didn't configure any HttpMessageConverter in my context file. If HttpMessageConverter and ContentNegotiatingViewResolver works together then how conversion is performed here ?
I've a separate class shown below.
I got only xml representation for both Urls http://localhost:8080/contextName/testContentNegotiation/coffee/mocha (.xml or .json ). I am not getting any idea how this is happening as I didn't configure any MessageConverter in context. if that's mandatory then why xml is returned for both Urls. What role @ResponseBody is playing in CoffeeController.getCoffeeInXML() method?
One more doubt what is role of defaultViews and viewResolvers properties of ContentNegotiatingViewResolver.
Ok this is probably going to be a long response so buckle your seat belt
ContentNegotiatingViewResolver does not resolve views itself but rather delegates to other view resolvers, like your MappingJacksonJsonView, or your MarshallingView. There are 2 strategies for determining the correct representation:
1. Distinct URI - This is where your request ends in .json or .xml
2. Content Negotiation - The correct media type is read off of the Accept Header of the request.
Now when you configured your view resolvers you specified the Content-Type(Media type) along with your keys for example
<entry key="json" value="application/json" />
Here what you have done is opted to use the URI strategy above. This line in turn mapped the .json file extension to the Content-Type of "application/json" . So if a request comes in with a .json file extension the ContentNegotiatingViewResolver will go down the ViewResolver chain and return the first View it finds that supports that content type, which will turn out to be MappingJacksonJsonView.
The ViewResolver chain I just mentioned is the same viewResolvers property on the ContentNegotiatingViewResolver that you asked about. If it makes it through the entire list of ViewResolvers and does not find a match it will go through the views specified by the DefaultViews property. Default views are singleton views that can render an appropriate response regardless of the logical view name. Both MappingJacksonJsonView and MarshallingView would be good examples of this.
Following is an example:
Now because in your example you did not define <property name="viewResolvers"> the ContentNegotiatingViewResolver will automatically use any ViewResolver that it can find defined in the application context.
If you chose to not include the file extension .json or .xml then it would consult the Accept Header to find the correct resolution. This is problematic at times since browsers will submit with an an Accept header similar to the following
This is impossible to to change with HTML, which is why for browser based applications the URI pattern is generally used. BTW this is also why your second example only returned XML (notice there is no json in that header, but more on that later)
Now lets talk about @ResponseBody-
When your method is annotated with @ResponseBody you are indicating that the return type should be written straight the the HTTP response body and not interpreted as a view name or placed in model. In order to facilitate this Spring converts your returned object to a response body by using a HttpMessageConverter.
Where did they come from? Spring registers several of these for you automatically including MappingJacksonHttpMessageConverter if Jackson is found on the classpath and Jaxb2RootElementHttpMessageConverter if JaxB is found on the class path.
Now unlike the ContentNegotiatingViewResolver file extensions don't help you here. All the resolution is made from the headers. If the Accept header of the request is set to "application/xml" (as would have been in your example), XML will be returned regardless of whether you put a .json on the end. To be clear and enforce correct mapping you should make use of the consumes and produces properties of the @RequestMapping.
For example take this method that takes an xml or json message posted in and returns some sort of response -
What this will do for you is when spring is mapping the request to this handler method it will ensure that the Content-Type and Accept headers of the request must be either "application/xml" or "application/json" for this method to process the request.
Now say this endpoint is being posted in via some jquery/ajax from my jsp. If I set my Content-Type to "application/json" and my Accept to "application/xml" this method will accept that request. It will assume that someOtherObject is json and will unmarshall it for me. It will do whatever it needs to do and use the Jaxb2RootElementHttpMessageConverter to write my instance of SomeObject as XML to the HTTP response.
To play with this more use this fire fox extension which allows you to set the headers: