Convertidores de mensajes HTTP con Spring Framework

1. Información general

Este artículo describe cómo configurar HttpMessageConverters en Spring .

En pocas palabras, podemos usar convertidores de mensajes para ordenar y deshacer los objetos Java hacia y desde JSON, XML, etc., a través de HTTP.

2. Los fundamentos

2.1. Habilitar Web MVC

Para empezar, la aplicación web debe configurarse con el soporte de Spring MVC. Una forma conveniente y muy personalizable de hacer esto es usar la anotación @EnableWebMvc :

@EnableWebMvc @Configuration @ComponentScan({ "com.baeldung.web" }) public class WebConfig implements WebMvcConfigurer { ... }

Tenga en cuenta que esta clase implementa WebMvcConfigurer , que nos permitirá cambiar la lista predeterminada de convertidores Http con la nuestra.

2.2. Los convertidores de mensajes predeterminados

De forma predeterminada, las siguientes instancias de HttpMessageConverter están prehabilitadas :

  • ByteArrayHttpMessageConverter - convierte matrices de bytes
  • StringHttpMessageConverter - convierte cadenas
  • ResourceHttpMessageConverter : convierte org.springframework.core.io.Resource para cualquier tipo de flujo de octetos
  • SourceHttpMessageConverter : convierte javax.xml.transform.Source
  • FormHttpMessageConverter : convierte los datos del formulario a / desde un MultiValueMap .
  • Jaxb2RootElementHttpMessageConverter : convierte objetos Java a / desde XML (se agrega solo si JAXB2 está presente en la ruta de clase)
  • MappingJackson2HttpMessageConverter - convierte JSON (agregado solo si Jackson 2 está presente en la ruta de clase)

  • MappingJacksonHttpMessageConverter - convierte JSON (agregado solo si Jackson está presente en la ruta de clase)
  • AtomFeedHttpMessageConverter : convierte las fuentes Atom (se agrega solo si Rome está presente en la ruta de clases)
  • RssChannelHttpMessageConverter : convierte fuentes RSS (se agrega solo si Rome está presente en la ruta de clases)

3. Comunicación cliente-servidor: solo JSON

3.1. Negociación de contenido de alto nivel

Cada implementación de HttpMessageConverter tiene uno o varios tipos MIME asociados.

Al recibir una nueva solicitud, Spring utilizará el encabezado " Aceptar " para determinar el tipo de medio con el que necesita responder .

Luego intentará encontrar un convertidor registrado que sea capaz de manejar ese tipo de medio específico. Finalmente, usará esto para convertir la entidad y devolver la respuesta.

El proceso es similar para recibir una solicitud que contiene información JSON. El marco utilizará el encabezado " Content-Type " para determinar el tipo de medio del cuerpo de la solicitud .

Luego buscará un HttpMessageConverter que pueda convertir el cuerpo enviado por el cliente en un objeto Java.

Aclaremos esto con un ejemplo rápido:

  • el Cliente envía una solicitud GET a / foos con el encabezado Accept configurado en application / json - para obtener todos los recursos de Foo como JSON
  • el Foo Spring Controller se activa y devuelve las correspondientes entidades Foo Java
  • Spring luego usa uno de los convertidores de mensajes de Jackson para asignar las entidades a JSON

Veamos ahora los detalles de cómo funciona esto y cómo podemos aprovechar las anotaciones @ResponseBody y @ RequestBody .

3.2. @ResponseCuerpo

@ResponseBody en un método Controller indica a Spring que el valor de retorno del método se serializa directamente en el cuerpo de la Respuesta HTTP . Como se mencionó anteriormente, el encabezado " Aceptar " especificado por el Cliente se utilizará para elegir el Convertidor Http apropiado para clasificar a la entidad.

Veamos un ejemplo simple :

@GetMapping("/{id}") public @ResponseBody Foo findById(@PathVariable long id) { return fooService.findById(id); }

Ahora, el cliente especificará el encabezado "Aceptar" para la aplicación / json en la solicitud - ejemplo de comando curl :

curl --header "Accept: application/json" //localhost:8080/spring-boot-rest/foos/1

La clase Foo :

public class Foo { private long id; private String name; }

Y el cuerpo de respuesta Http:

{ "id": 1, "name": "Paul", }

3.3. @RequestCuerpo

Podemos usar la anotación @RequestBody en el argumento de un método Controller para indicar que el cuerpo de la solicitud HTTP está deserializado en esa entidad Java en particular . Para determinar el convertidor apropiado, Spring utilizará el encabezado "Content-Type" de la solicitud del cliente.

Veamos un ejemplo:

@PutMapping("/{id}") public @ResponseBody void update(@RequestBody Foo foo, @PathVariable String id) { fooService.update(foo); }

A continuación, consumamos esto con un objeto JSON; estamos especificando "Content-Type " para que sea application / json :

curl -i -X PUT -H "Content-Type: application/json" -d '{"id":"83","name":"klik"}' //localhost:8080/spring-boot-rest/foos/1

Recibimos un 200 OK, una respuesta exitosa:

HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Length: 0 Date: Fri, 10 Jan 2014 11:18:54 GMT

4. Configuración de convertidores personalizados

También podemos personalizar los convertidores de mensajes implementando la interfaz WebMvcConfigurer y anulando el método configureMessageConverters :

@EnableWebMvc @Configuration @ComponentScan({ "com.baeldung.web" }) public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters( List
    
      converters) { messageConverters.add(createXmlHttpMessageConverter()); messageConverters.add(new MappingJackson2HttpMessageConverter()); } private HttpMessageConverter createXmlHttpMessageConverter() { MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(); XStreamMarshaller xstreamMarshaller = new XStreamMarshaller(); xmlConverter.setMarshaller(xstreamMarshaller); xmlConverter.setUnmarshaller(xstreamMarshaller); return xmlConverter; } }
    

In this example, we're creating a new converter – the MarshallingHttpMessageConverter – and using the Spring XStream support to configure it. This allows a great deal of flexibility since we're working with the low-level APIs of the underlying marshalling framework – in this case XStream – and we can configure that however we want.

Note that this example requires adding the XStream library to the classpath.

Also be aware that by extending this support class, we're losing the default message converters which were previously pre-registered.

We can of course now do the same for Jackson – by defining our own MappingJackson2HttpMessageConverter. We can now set a custom ObjectMapper on this converter and have it configured as we need to.

In this case, XStream was the selected marshaller/unmarshaller implementation, but others like CastorMarshaller can be used as well.

At this point – with XML enabled on the back end – we can consume the API with XML Representations:

curl --header "Accept: application/xml" //localhost:8080/spring-boot-rest/foos/1

4.1. Spring Boot Support

If we're using Spring Boot we can avoid implementing the WebMvcConfigurer and adding all the Message Converters manually as we did above.

We can just define different HttpMessageConverter beans in the context, and Spring Boot will add them automatically to the autoconfiguration that it creates:

@Bean public HttpMessageConverter createXmlHttpMessageConverter() { MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(); // ... return xmlConverter; }

5. Using Spring’s RestTemplate With Http Message Converters

As well as with the server side, Http Message Conversion can be configured in the client side on the Spring RestTemplate.

We're going to configure the template with the “Accept” and “Content-Type” headers when appropriate. Then we'll try to consume the REST API with full marshalling and unmarshalling of the Foo Resource – both with JSON and with XML.

5.1. Retrieving the Resource With No Accept Header

@Test public void testGetFoo() { String URI = “//localhost:8080/spring-boot-rest/foos/{id}"; RestTemplate restTemplate = new RestTemplate(); Foo foo = restTemplate.getForObject(URI, Foo.class, "1"); Assert.assertEquals(new Integer(1), foo.getId()); }

5.2. Retrieving a Resource With application/xml Accept Header

Let's now explicitly retrieve the Resource as an XML Representation. We're going to define a set of Converters and set these on the RestTemplate.

Because we're consuming XML, we're going to use the same XStream marshaller as before:

@Test public void givenConsumingXml_whenReadingTheFoo_thenCorrect() { String URI = BASE_URI + "foos/{id}"; RestTemplate restTemplate = new RestTemplate(); restTemplate.setMessageConverters(getMessageConverters()); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML)); HttpEntity entity = new HttpEntity(headers); ResponseEntity response = restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1"); Foo resource = response.getBody(); assertThat(resource, notNullValue()); } private List
    
      getMessageConverters() { XStreamMarshaller marshaller = new XStreamMarshaller(); MarshallingHttpMessageConverter marshallingConverter = new MarshallingHttpMessageConverter(marshaller); List
     
       converters = ArrayList
      
       (); converters.add(marshallingConverter); return converters; }
      
     
    

5.3. Retrieving a Resource With application/json Accept Header

Similarly, let's now consume the REST API by asking for JSON:

@Test public void givenConsumingJson_whenReadingTheFoo_thenCorrect() { String URI = BASE_URI + "foos/{id}"; RestTemplate restTemplate = new RestTemplate(); restTemplate.setMessageConverters(getMessageConverters()); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity entity = new HttpEntity(headers); ResponseEntity response = restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1"); Foo resource = response.getBody(); assertThat(resource, notNullValue()); } private List
    
      getMessageConverters() { List
     
       converters = new ArrayList
      
       (); converters.add(new MappingJackson2HttpMessageConverter()); return converters; }
      
     
    

5.4. Update a Resource With XML Content-Type

Finally, let's also send JSON data to the REST API and specify the media type of that data via the Content-Type header:

@Test public void givenConsumingXml_whenWritingTheFoo_thenCorrect() { String URI = BASE_URI + "foos/{id}"; RestTemplate restTemplate = new RestTemplate(); restTemplate.setMessageConverters(getMessageConverters()); Foo resource = new Foo(4, "jason"); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); headers.setContentType((MediaType.APPLICATION_XML)); HttpEntity entity = new HttpEntity(resource, headers); ResponseEntity response = restTemplate.exchange( URI, HttpMethod.PUT, entity, Foo.class, resource.getId()); Foo fooResponse = response.getBody(); Assert.assertEquals(resource.getId(), fooResponse.getId()); }

What's interesting here is that we're able to mix the media types – we're sending XML data but we're waiting for JSON data back from the server. This shows just how powerful the Spring conversion mechanism really is.

6. Conclusion

In this tutorial, we looked at how Spring MVC allows us to specify and fully customize Http Message Converters to automatically marshall/unmarshall Java Entities to and from XML or JSON. This is, of course, a simplistic definition, and there is so much more that the message conversion mechanism can do – as we can see from the last test example.

We have also looked at how to leverage the same powerful mechanism with the RestTemplate client – leading to a fully type-safe way of consuming the API.

Como siempre, el código presentado en este artículo está disponible en Github.