HTTP-Nachrichtenkonverter mit dem Spring Framework

1. Übersicht

Dieser Artikel beschreibt, wie Sie HttpMessageConverters im Frühjahr konfigurieren .

Einfach ausgedrückt, wir können Nachrichtenkonverter verwenden, um Java-Objekte von und zu JSON, XML usw. über HTTP zu marshallen und zu entfernen.

2. Die Grundlagen

2.1. Aktivieren Sie Web MVC

Zunächst muss die Webanwendung mit Spring MVC-Unterstützung konfiguriert werden. Eine bequeme und sehr anpassbare Möglichkeit, dies zu tun, ist die Verwendung der Annotation @EnableWebMvc :

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

Beachten Sie, dass diese Klasse WebMvcConfigurer implementiert, mit dem wir die Standardliste der HTTP-Konverter durch unsere eigene Liste ändern können.

2.2. Die Standardnachrichtenkonverter

Standardmäßig sind die folgenden Instanzen von HttpMessageConverter voraktiviert:

  • ByteArrayHttpMessageConverter - konvertiert Byte-Arrays
  • StringHttpMessageConverter - konvertiert Strings
  • ResourceHttpMessageConverter - konvertiert org.springframework.core.io.Resource für jede Art von Oktett-Stream
  • SourceHttpMessageConverter - konvertiert javax.xml.transform.Source
  • FormHttpMessageConverter - konvertiert Formulardaten in / aus einer MultiValueMap .
  • Jaxb2RootElementHttpMessageConverter - konvertiert Java-Objekte in / aus XML (wird nur hinzugefügt, wenn JAXB2 im Klassenpfad vorhanden ist).
  • MappingJackson2HttpMessageConverter - konvertiert JSON (nur hinzugefügt, wenn Jackson 2 im Klassenpfad vorhanden ist)

  • MappingJacksonHttpMessageConverter - konvertiert JSON (wird nur hinzugefügt, wenn Jackson im Klassenpfad vorhanden ist)
  • AtomFeedHttpMessageConverter - konvertiert Atom-Feeds (nur hinzugefügt, wenn Rom im Klassenpfad vorhanden ist)
  • RssChannelHttpMessageConverter - konvertiert RSS-Feeds (nur hinzugefügt, wenn Rom im Klassenpfad vorhanden ist)

3. Client-Server-Kommunikation - Nur JSON

3.1. Verhandlung über Inhalte auf hoher Ebene

Jeder HttpMessageConverter- Implementierung sind ein oder mehrere MIME-Typen zugeordnet.

Beim Empfang einer neuen Anforderung verwendet Spring den Header " Accept ", um den Medientyp zu bestimmen, mit dem es antworten muss .

Anschließend wird versucht, einen registrierten Konverter zu finden, der diesen bestimmten Medientyp verarbeiten kann. Schließlich wird dies verwendet, um die Entität zu konvertieren und die Antwort zurückzusenden.

Der Vorgang zum Empfangen einer Anforderung, die JSON-Informationen enthält, ist ähnlich. Das Framework verwendet den Header " Content-Type ", um den Medientyp des Anforderungshauptteils zu bestimmen .

Anschließend wird nach einem HttpMessageConverter gesucht , der den vom Client gesendeten Text in ein Java-Objekt konvertieren kann.

Lassen Sie uns dies anhand eines kurzen Beispiels verdeutlichen:

  • Der Client sendet eine GET-Anforderung an / foos, wobei der Accept- Header auf application / json festgelegt ist , um alle Foo- Ressourcen als JSON abzurufen
  • Der Foo Spring Controller wird getroffen und gibt die entsprechenden Foo Java-Entitäten zurück
  • Spring verwendet dann einen der Jackson-Nachrichtenkonverter, um die Entitäten in JSON zu marshallen

Schauen wir uns nun die Besonderheiten an, wie dies funktioniert - und wie wir die Annotationen @ResponseBody und @ RequestBody nutzen können .

3.2. @ResponseBody

@ResponseBody auf einer Controller-Methode zeigt Spring an, dass der Rückgabewert der Methode direkt in den Hauptteil der HTTP-Antwort serialisiert wird . Wie oben erläutert, wird der vom Client angegebene Header " Accept " verwendet, um den geeigneten HTTP-Konverter zum Marshalling der Entität auszuwählen.

Schauen wir uns ein einfaches Beispiel an :

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

Jetzt gibt der Client den Header "Accept" für application / json im Befehl request - example curl an :

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

Die Foo- Klasse:

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

Und der HTTP-Antwortkörper:

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

3.3. @RequestBody

Wir können die Annotation @RequestBody für das Argument einer Controller-Methode verwenden, um anzugeben, dass der Hauptteil der HTTP-Anforderung für diese bestimmte Java-Entität deserialisiert ist . Um den geeigneten Konverter zu ermitteln, verwendet Spring den Header "Content-Type" aus der Clientanforderung.

Schauen wir uns ein Beispiel an:

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

Als nächstes verwenden wir dies mit einem JSON-Objekt. Wir geben "Content-Type " als application / json an :

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

Wir bekommen 200 OK zurück - eine erfolgreiche Antwort:

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

4. Konfiguration der benutzerdefinierten Konverter

Sie können die Nachrichtenkonverter auch anpassen, indem Sie die WebMvcConfigurer- Schnittstelle implementieren und die configureMessageConverters- Methode überschreiben :

@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 diesem Beispiel erstellen wir einen neuen Konverter - den MarshallingHttpMessageConverter - und konfigurieren ihn mithilfe der Spring XStream-Unterstützung. Dies ermöglicht ein hohes Maß an Flexibilität, da wir mit den Low-Level-APIs des zugrunde liegenden Marshalling-Frameworks - in diesem Fall XStream - arbeiten und dies nach Belieben konfigurieren können.

Beachten Sie, dass für dieses Beispiel die XStream-Bibliothek zum Klassenpfad hinzugefügt werden muss.

Beachten Sie auch, dass durch die Erweiterung dieser Supportklasse die zuvor vorregistrierten Standardnachrichtenkonverter verloren gehen.

Das können wir jetzt natürlich auch für Jackson tun - indem wir unseren eigenen MappingJackson2HttpMessageConverter definieren. Wir können jetzt einen benutzerdefinierten ObjectMapper für diesen Konverter festlegen und ihn nach Bedarf konfigurieren lassen.

In diesem Fall war XStream die ausgewählte Marshaller / Unmarshaller-Implementierung, aber auch andere wie CastorMarshaller können verwendet werden.

An diesem Punkt - mit aktiviertem XML im Back-End - können wir die API mit XML-Darstellungen verwenden:

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

4.1. Spring Boot-Unterstützung

Wenn wir Spring Boot verwenden, können wir vermeiden, den WebMvcConfigurer zu implementieren und alle Nachrichtenkonverter manuell hinzuzufügen, wie oben beschrieben.

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.

Wie immer ist der in diesem Artikel vorgestellte Code auf Github verfügbar.