Spring in Practice

Willie Wheeler's Spring blog

Supporting XML and JSON Web Service Endpoints in Spring 3.1 Using @ResponseBody

| Comments

In an earlier post I explained how to avoid parallel JAXB2/Jackson annotations when supporting both XML and JSON web service endpoints. Basically the idea was to configure the Jackson ObjectMapper to understand JAXB2 annotations.

The problem

One of the things that was a little unsatisfactory about that post was that I was using views to generate the XML and JSON representations. We can do that, of course, but a more direct approach to generating the desired data payloads is to use @ResponseBody in conjunction with Spring’s HttpMessageConverters. The idea here is that handler methods simply return the object to be mapped (whether to XML or to JSON), and then HTTP message converters sitting on the app context map the object to XML or JSON. Correspondingly, there are HTTP message converters for both JAXB2 and Jackson (among several others). I say this is more direct because there’s no need to involve a model or a view at all: the controller simply returns the object and the converters do the rest.

In Spring 3.0 this was somewhat challenging to pull off, because both the JAXB2 message converter and the Jackson message converter are able to map the object, and so whichever message converter appears first in the list (the JAXB2 converter, it seems) would always map the object. So we ended up either having to treat JSON as a special case (invoke the ObjectMapper directly), or else just use views after all.

In Spring 3.1 things are a lot better: we can use the @ResponseBody and HTTP message converter approach quite cleanly, owing to an enhancement to @RequestMapping and also to an enhancement to the <mvc:annotation-driven> configuration.

Let’s see how it works.

Implementing the controller

We’ll look at the controller code first:

@RequestMapping(
    value = "/{id}.json",
    method = RequestMethod.GET,
    produces = "application/json")
@ResponseBody
public Person getDetailsAsJson(@PathVariable Long id) {
    return personRepo.findOne(id);
}

@RequestMapping(
    value = "/{id}.xml",
    method = RequestMethod.GET,
    produces = "application/xml")
@ResponseBody
public Person getDetailsAsXml(@PathVariable Long id) {
    return personRepo.findOne(id);
}

To use the @ResponseBody approach, we obviously need to annotate the handler methods with that annotation, so that’s what we’ve done here. The other thing we do here is return the actual entity (in this case, a Person) from the method instead of returning a logical view name.

We’ve also specified the extension (either .json or .xml) in the value to route requests correctly.

So far, everything we've described was available in Spring 3.0. But notice the new produces element, introduced with Spring 3.1, in the @RequestMapping annotation. This element has two effects. First, it excludes requests with Accepts headers incompatible with the specified media type. Second, it ensures that the generated response is consistent with the specified media type. It’s this second effect that we care about, because this is what allows Spring to figure out which HTTP message converter to use, even when we’re using JAXB2 annotations for both XML and JSON mapping.

Configuration

We need some configuration too. Here are the relevant bits. Be sure you’re using Spring 3.1, that you’ve declared the oxm and mvc namespaces, etc.

<oxm:jaxb2-marshaller id="marshaller">
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Person" />
</oxm:jaxb2-marshaller>
    
<bean id="jaxbAnnIntrospector" class="org.codehaus.jackson.xc.JaxbAnnotationIntrospector" />
<bean id="jacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper">
    <property name="serializationConfig.annotationIntrospector" ref="jaxbAnnIntrospector" />
    <property name="deserializationConfig.annotationIntrospector" ref="jaxbAnnIntrospector" />
</bean>

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"
            p:objectMapper-ref="jacksonObjectMapper" />
    </mvc:message-converters>
</mvc:annotation-driven>

The JAXB2 marshaller and Jackson ObjectMapper are the same as they were in my earlier post. The thing that’s different is the <mvc:annotation-driven> definition, and specifically, my inclusion of the new Spring 3.1 <mvc:message-converters> configuration. This allows us to define converters that override the defaults. Here I want to override the default Jackson converter with one that knows about my JAXB2-enabled ObjectMapper.

You don’t need to include XML or JSON views anymore. If you don’t have anything other than the normal JSP view, you can probably get rid of the ContentNegotiatingViewResolver entirely.

The end result

The result is that we can now generate XML and JSON payloads in a simple and consistent fashion. There’s no more need for models and views here (at least with respect to generating XML and JSON), and there’s no need to invoke ObjectMappers directly or anything like that.

Comments