Convertisseurs de messages HTTP avec la structure Spring

Convertisseurs de messages HTTP avec la structure Spring

1. Vue d'ensemble

Cet article décrithow to Configure HttpMessageConverters in Spring.

En termes simples, nous pouvons utiliser des convertisseurs de messages pour les objets Java marshall et unmarshall de et vers JSON, XML, etc. - via HTTP.

Lectures complémentaires:

Spring MVC Content Negotiation

Guide de configuration de la négociation de contenu dans une application Spring MVC et d'activation et de désactivation des différentes stratégies disponibles.

Read more

Renvoi de données image / média avec Spring MVC

L'article présente les différentes solutions permettant de renvoyer une image (ou un autre support) avec Spring MVC, ainsi que les avantages et les inconvénients de chaque approche.

Read more

Formats de données binaires dans une API REST Spring

Dans cet article, nous explorons comment configurer le mécanisme Spring REST pour qu'il utilise les formats de données binaires illustrés avec Kryo. De plus, nous montrons comment prendre en charge plusieurs formats de données avec les tampons de protocole Google.

Read more

2. Les bases

2.1. Activer Web MVC

Pour commencer, l'application Web doit êtreconfigured with Spring MVC support. Une manière pratique et très personnalisable de le faire est d'utiliser l'annotation@EnableWebMvc:

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

Notez que cette classe implémenteWebMvcConfigurer - ce qui nous permettra de changer la liste par défaut des convertisseurs Http avec la nôtre.

2.2. Les convertisseurs de messages par défaut

Par défaut, les instancesHttpMessageConverters suivantes sont pré-activées:

  • ByteArrayHttpMessageConverter - convertit les tableaux d'octets

  • StringHttpMessageConverter - convertit les chaînes

  • ResourceHttpMessageConverter - convertitorg.springframework.core.io.Resource pour tout type de flux d'octets

  • SourceHttpMessageConverter - convertitjavax.xml.transform.Source

  • FormHttpMessageConverter - convertit les données du formulaire en / à partir d'unMultiValueMap<String, String>.

  • Jaxb2RootElementHttpMessageConverter - convertit les objets Java vers / depuis XML (ajouté uniquement si JAXB2 est présent sur le chemin de classe)

  • MappingJackson2HttpMessageConverter - convertit JSON (ajouté uniquement si Jackson 2 est présent sur le chemin de classe) __

  • MappingJacksonHttpMessageConverter - convertit JSON (ajouté uniquement si Jackson est présent sur le chemin de classe)

  • AtomFeedHttpMessageConverter - convertit les flux Atom (ajouté uniquement si Rome est présent sur le chemin de classe)

  • RssChannelHttpMessageConverter - convertit les flux RSS(added only if Rome is present on the classpath)

3. Communication client-serveur - JSON uniquement

3.1. Négociation de contenu de haut niveau

Chaque implémentation deHttpMessageConverter a un ou plusieurs types MIME associés.

Lors de la réception d'une nouvelle demande,Spring will use the “Accept” header to determine the media type that it needs to respond with.

Il essaiera ensuite de trouver un convertisseur enregistré capable de gérer ce type de support spécifique. Enfin, il l'utilisera pour convertir l'entité et renvoyer la réponse.

Le processus est similaire pour recevoir une demande contenant des informations JSON. Le framework serause the “*Content-Type ”en-tête pour déterminer le type de média du corps de la requête *.

Il recherchera ensuite unHttpMessageConverter capable de convertir le corps envoyé par le client en objet Java.

Clarifions cela avec un exemple rapide:

  • le client envoie une requête GET à/foos avec l'en-têteAccept défini surapplication/json - pour obtenir toutes les ressourcesFoo en JSON

  • le Spring ControllerFoo est touché et renvoie les entités JavaFoo correspondantes

  • Spring utilise ensuite l’un des convertisseurs de messages de Jackson pour rassembler les entités en JSON.

Examinons maintenant les détails de son fonctionnement - et comment nous pouvons tirer parti des annotations@ResponseBody et @RequestBody.

3.2. @ResponseBody

@ResponseBody sur une méthode de contrôleur indique à Spring quethe return value of the method is serialized directly to the body of the HTTP Response. Comme discuté ci-dessus, l'en-tête «Accept» spécifié par le client sera utilisé pour choisir le convertisseur Http approprié pour rassembler l'entité.

Regardons un exemple simple:

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

Maintenant, le client va spécifier l'en-tête «Accept» àapplication/json dans la requête - exemple de commandecurl:

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

La classeFoo:

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

Et le corps de réponse Http:

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

3.3. @RequestBody

Nous pouvons utiliser l'annotation@RequestBody sur l'argument d'une méthode Controller pour indiquerthat the body of the HTTP Request is deserialized to that particular Java entity. Pour déterminer le convertisseur approprié, Spring utilisera l'en-tête «Content-Type» de la demande du client.

Regardons un exemple:

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

Ensuite, consommons ceci avec un objet JSON - nous spécifions "Content-Type comme étantapplication/json:

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

Nous recevons un 200 OK - une réponse réussie:

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

4. Configuration des convertisseurs personnalisés

On peut aussicustomize the message converters by implementing the WebMvcConfigurer interface et redéfinir la méthodeconfigureMessageConverters:

@EnableWebMvc
@Configuration
@ComponentScan({ "com.example.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;
    }
}


Et voici la configuration XML correspondante:



    
        
        
            
            
        
    


Dans cet exemple, nous créons un nouveau convertisseur - leMarshallingHttpMessageConverter - et utilisons le support Spring XStream pour le configurer. Cela permet une grande flexibilité depuiswe’re working with the low-level APIs of the underlying marshalling framework - dans ce cas XStream - et nous pouvons le configurer comme nous le voulons.

Notez que cet exemple nécessite l'ajout de la bibliothèque XStream au chemin de classe.

Sachez également qu'en étendant cette classe de support,we’re losing the default message converters which were previously pre-registered.

Nous pouvons bien sûr faire la même chose pour Jackson - en définissant nos propresMappingJackson2HttpMessageConverter. Nous pouvons maintenant définir unObjectMapper personnalisé sur ce convertisseur et le configurer selon nos besoins.

Dans ce cas, XStream était l'implémentation marshaller / unmarshaller sélectionnée, maisothers commeCastorMarshaller peut également être utilisé.

À ce stade - avec XML activé sur le back-end -, nous pouvons utiliser l'API avec des représentations XML:

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

4.1. Prise en charge du démarrage du printemps

Si nous utilisons Spring Boot, nous pouvons éviter d'implémenter le sableWebMvcConfigurer en ajoutant manuellement tous les convertisseurs de messages comme nous l'avons fait ci-dessus.

Nous pouvons simplement définir différents sbeansHttpMessageConverter dans le contexte, et Spring Boot les ajoutera automatiquement à l'autoconfiguration qu'il crée:

@Bean
public HttpMessageConverter createXmlHttpMessageConverter() {
    MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();

    // ...

    return xmlConverter;
}





5. Utilisation desRestTemplate de Spring avec les convertisseurs de messages Http

En plus du côté serveur, la conversion de message Http peut être configurée côté client sur les SpringRestTemplate.

Nous allons configurer le modèle avec les en-têtes «Accept» et «Content-Type» le cas échéant. Ensuite, nous essaierons de consommer l'API REST avec un marshalling et une démarshalling complets de la ressourceFoo - à la fois avec JSON et avec XML.

5.1. Récupération de la ressource sans en-têteAccept

@Test
public void testGetFoo() {
    String URI = “http://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. Récupération d'une ressource avec l'en-tête d'acceptationapplication/xml

Récupérons maintenant explicitement la ressource en tant que représentation XML. Nous allons définir un ensemble de convertisseurs et les définir sur lesRestTemplate.

Étant donné que nous consommons du XML, nous allons utiliser le même marshaller XStream qu'auparavant:

@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. Récupération d'une ressource avec l'en-tête d'acceptationapplication/json

De même, consommons maintenant l'API REST en demandant 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. Mettre à jour une ressource avec XMLContent-Type

Enfin, envoyons également des données JSON à l'API REST et spécifions le type de média de ces données via l'en-têteContent-Type:

@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());
}

Ce qui est intéressant ici, c'est que nous sommes en mesure de mélanger les types de médias -we’re sending XML data but we’re waiting for JSON data back from the server. Cela montre à quel point le mécanisme de conversion Spring est puissant.

6. Conclusion

Dans ce didacticiel, nous avons examiné comment Spring MVC nous permet de spécifier et de personnaliser entièrement les convertisseurs de messages Http enautomatically marshall/unmarshall Java Entities to and from XML or JSON. Il s’agit bien entendu d’une définition simpliste et le mécanisme de conversion des messages peut faire bien plus encore, comme le montre le dernier exemple de test.

Nous avons également examiné comment tirer parti du même mécanisme puissant avec le clientRestTemplate - menant à une manière totalement sécurisée de consommer l'API.

Comme toujours, le code présenté dans cet article est disponibleover on Github.