Content negotiation is a mechanism that makes it possible to serve different representations of the same resource. For example, so far we have shown our product detail page in a JSP representation. What if we want to represent the same content in an XML format. Similarly, what if we want the same content in a JSON format? Here comes Spring MVC's ContentNegotiatingViewResolver
(org.springframework.web.servlet.view.ContentNegotiatingViewResolver
) to help us.
The XML and JSON formats are popular data interchange formats that are heavily used in web service communications. Using ContentNegotiatingViewResolver
, we can incorporate many Views such as MappingJacksonJsonView
(for JSON) and MarshallingView
(for XML) to represent the same product information in a XML or JSON format.
ContentNegotiatingViewResolver
does not resolve Views itself, but rather delegates to other view resolvers based on the request. Now, let's add a content negotiation capability to our application:
pom.xml
, which you can find under the project root directory.pom.xml
; select the Dependencies tab and click on the Add button of the Dependencies section.org.springframework
, in Artifact Id enter spring-oxm
, in Version enter 4.3.0.RELEASE
, select Scope as compile, then click on the OK button.org.codehaus.jackson
as Group Id, jackson-mapper-asl
as Artifact Id, 1.9.10
as Version, and select Scope as compile. Then click on the OK button.com.fasterxml.jackson.core
as Group Id, jackson-databind
as Artifact Id , 2.8.0
as Version, and select Scope as compile. Then click on the OK button and save pom.xml
.WebApplicationContextConfig.java
) for the JSON View as follows:@Bean public MappingJackson2JsonView jsonView() { MappingJackson2JsonView jsonView = new MappingJackson2JsonView(); jsonView.setPrettyPrint(true); return jsonView; }
@Bean public MarshallingView xmlView() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(Product.class); MarshallingView xmlView = new MarshallingView(marshaller); return xmlView; }
ContentNegotiatingViewResolver
in our WebApplicationContextConfig
web application context configuration file as follows:@Bean public ViewResolver contentNegotiatingViewResolver( ContentNegotiationManager manager) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager(manager); ArrayList<View> views = new ArrayList<>(); views.add(jsonView()); views.add(xmlView()); resolver.setDefaultViews(views); return resolver; }
Product.java
) and add the @XmlRootElement
annotation at the top of the class.@XmlTransient
annotation at the top of the getProductImage
()
method and add another annotation @JsonIgnore
on top of the productImage
field.http://localhost:8080/webstore/market/product?id=P1235
. You will now be able to view the details page of the product with the ID P1234
..xml
extension: http://localhost:8080/webstore/market/product.xml?id=P1235
. You should be able to see the same content in the XML format as shown in the following screenshot:.json
extension: http://localhost:8080/webstore/market/product.json?id=P1235
. You will be able to see the JSON representation of that content as shown in the following screenshot:Since we want an XML representation of our model data to convert our model objects into XML, we need Spring's object/XML mapping support—that's why we added the dependency for spring-oxm.jar
through steps 1 to 3. The spring-oxm
notation will help us convert an XML document to and from a Java object.
Similarly, to convert model objects into JSON, Spring MVC will use jackson-mapper-asl.jar
and jackson-databind.jar
; thus we need those jars in our project as well. In steps 4 and 5, we just added the dependency configuration for those jars.
If you remember in our servlet context (servlet-context.xml
), we already defined InternalResourceViewResolver
as our view resolver to resolve JSP-based Views, but this time we want a view resolver to resolve XML and JSON Views. That's why in step 8 we configured ContentNegotiatingViewResolver
(org.springframework.web.servlet.view.ContentNegotiatingViewResolver
) in our servlet context.
As I already mentioned, ContentNegotiatingViewResolver
does not resolve Views itself but rather it delegates to other Views based on the request, so we need to introduce other Views to ContentNegotiatingViewResolver
. How we do that is through the setDefaultViews
method in ContentNegotiatingViewResolver
:
@Bean public ViewResolver contentNegotiatingViewResolver( ContentNegotiationManager manager) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager(manager); ArrayList<View> views = new ArrayList<>(); views.add(jsonView()); views.add(xmlView()); resolver.setDefaultViews(views); return resolver; }
To configure bean references for jsonView
and xmlView
inside ContentNegotiatingViewResolver
, we need a bean definition for those references. That is the reason we defined those beans in steps 6 and 7.
The xmlView
bean configuration especially has one important property to be set called classesToBeBound
; this lists the domain objects that require XML conversion during the request processing. Since our product domain object requires XML conversion, we added com.packt.webstore.domain.Product
to the list classesToBeBound
:
@Bean public MarshallingView xmlView() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(Product.class); MarshallingView xmlView = new MarshallingView(marshaller); return xmlView; }
In order to convert it to XML, we need to give MarshallingView
one more hint to identify the root XML element in the Product
domain object. This is why in step 9 we annotated our class with the @XmlRootElement
(javax.xml.bind.annotation.XmlRootElement
) annotation.
In step 10, we added the @XmlTransient
(javax.xml.bind.annotation.XmlTransient
) annotation on top of the getProductImage
()
method and added another annotation—@JsonIgnore
(org.codehaus.jackson.annotate.JsonIgnore
)—on top of the productImage
field. This is because we don't want to represent the product image as part of the XML View or the JSON View since both formats are purely a text-based representation, and it is not possible to represent images in text.
In step 10, we simply accessed our product details page in the regular way by firing the web request (http://localhost:8080/webstore/products/product?id=P1234
) from the browser. We could see the normal JSP View, as expected.
In step 11, we just changed the URL slightly by adding an .xml
extension to the request path: http://localhost:8080/webstore/market/products/product.xml?id=P1235
. This time we were able to see the same product information in the XML format.
Similarly for the JSON View, we changed the extension for the .json
path to http://localhost:8080/webstore/market/products/product.json?id=P1235
and we were able to see the JSON representation of the same product information.