Преобразуватели на съобщения Http с Spring Framework

1. Общ преглед

Тази статия описва как да конфигурирате HttpMessageConverters през пролетта .

Най-просто казано, можем да използваме преобразуватели на съобщения за маршалиране и демаршалиране на Java Обекти към и от JSON, XML и т.н. - чрез HTTP.

2. Основите

2.1. Активирайте Web MVC

Като начало, уеб приложението трябва да бъде конфигурирано с поддръжка на Spring MVC. Удобен и много персонализиран начин да направите това е да използвате анотацията @EnableWebMvc :

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

Обърнете внимание, че този клас изпълнява WebMvcConfigurer - което ще ни позволи да променим списъка по подразбиране на Http конверторите с нашия собствен.

2.2. Преобразувателите на съобщения по подразбиране

По подразбиране следните екземпляри на HttpMessageConverter са предварително активирани:

  • ByteArrayHttpMessageConverter - преобразува байтови масиви
  • StringHttpMessageConverter - преобразува низове
  • ResourceHttpMessageConverter - преобразува org.springframework.core.io.Resource за всякакъв вид октетен поток
  • SourceHttpMessageConverter - преобразува javax.xml.transform.Source
  • FormHttpMessageConverter - преобразува данните от формуляра в / от MultiValueMap .
  • Jaxb2RootElementHttpMessageConverter - преобразува Java обекти в / от XML (добавя се само ако JAXB2 присъства в пътя на класа)
  • MappingJackson2HttpMessageConverter - преобразува JSON (добавя се само ако Jackson 2 присъства в пътя на класа)

  • MappingJacksonHttpMessageConverter - преобразува JSON (добавя се само ако Jackson присъства в пътя на класа)
  • AtomFeedHttpMessageConverter - преобразува Atom емисии (добавя се само ако Рим присъства в пътя на класа)
  • RssChannelHttpMessageConverter - преобразува RSS емисии (добавя се само ако Рим присъства в пътя на класа)

3. Комуникация клиент-сървър - само за JSON

3.1. Преговори за съдържание на високо ниво

Всяка реализация на HttpMessageConverter има един или няколко свързани MIME типа.

При получаване на нова заявка Spring ще използва заглавката „ Приемам “, за да определи типа носител, с който трябва да отговори .

След това ще се опита да намери регистриран преобразувател, който може да обработва този специфичен тип носител. И накрая, той ще използва това, за да преобразува обекта и да изпрати обратно отговора.

Процесът е подобен при получаване на заявка, която съдържа JSON информация. Рамката ще използва заглавката Content-Type ”, за да определи медийния тип на тялото на заявката .

След това ще търси HttpMessageConverter, който може да преобразува тялото, изпратено от клиента, в Java Object.

Нека изясним това с бърз пример:

  • клиентът изпраща GET заявка до / foos с заглавката Accept, зададена на application / json - за да получи всички ресурси на Foo като JSON
  • на Foo Пролет Контролерът е хит и се връща на съответните Foo Java лица
  • След това Spring използва един от преобразувателите на съобщения на Джаксън, за да разпредели обектите към JSON

Нека сега разгледаме спецификата на това как работи - и как можем да използваме анотациите @ResponseBody и @ RequestBody .

3.2. @ResponseBody

@ResponseBody на метод на контролер показва на Spring, че връщаната стойност на метода е сериализирана директно в тялото на HTTP отговора . Както беше обсъдено по-горе, заглавката „ Приемам “, посочена от клиента, ще бъде използвана за избор на подходящия конвертор на Http за маршалиране на обекта.

Нека разгледаме един прост пример :

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

Сега клиентът ще посочи заглавката „Приемам“ към application / json в заявката - пример команда curl :

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

Класът Foo :

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

И Http тялото за отговор:

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

3.3. @RequestBody

Можем да използваме анотацията @RequestBody върху аргумента на метод Controller, за да посочим , че тялото на HTTP заявката е десериализирано към конкретния Java обект . За да определи подходящия конвертор, Spring ще използва заглавката “Content-Type” от заявката на клиента.

Нека разгледаме един пример:

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

След това нека използваме това с JSON обект - ние посочваме „Content-Type да бъде application / json :

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

Връщаме 200 OK - успешен отговор:

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

4. Конфигурация на персонализирани преобразуватели

Също така можем да персонализираме преобразувателите на съобщения, като внедряваме интерфейса WebMvcConfigurer и заместваме метода 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; } }
    

В този пример създаваме нов конвертор - MarshallingHttpMessageConverter - и използваме поддръжката Spring XStream, за да го конфигурираме. Това позволява голяма доза гъвкавост, тъй като работим с API на ниско ниво на основната рамка за маршалиране - в случая XStream - и можем да конфигурираме това, което искаме.

Имайте предвид, че този пример изисква добавяне на библиотека XStream към пътя на класа.

Също така имайте предвид, че разширявайки този клас за поддръжка, ние губим преобразувателите на съобщения по подразбиране, които преди това са били предварително регистрирани.

Разбира се, сега можем да направим същото за Джаксън - като дефинираме собствения си MappingJackson2HttpMessageConverter. Вече можем да зададем персонализиран ObjectMapper на този конвертор и да го конфигурираме според нуждите ни.

В този случай XStream е избраната реализация на маршалер / демаршалер, но могат да се използват и други като CastorMarshaller .

В този момент - с активиран XML на задната страна - можем да използваме API с XML представления:

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

4.1. Поддръжка на пролетно зареждане

Ако използваме Spring Boot, можем да избегнем внедряването на WebMvcConfigurer и ръчното добавяне на всички преобразуватели на съобщения, както направихме по-горе.

Можем просто да дефинираме различни зърна HttpMessageConverter в контекста и Spring Boot ще ги добави автоматично към автоконфигурацията, която създава:

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

5. Използване на Spring's RestTemplate с преобразуватели на съобщения Http

Освен със сървъра, преобразуването на съобщения Http може да бъде конфигурирано и от страна на клиента на Spring RestTemplate .

Ще конфигурираме шаблона с заглавките „ Приемам “ и „ Тип съдържание “, когато е подходящо. След това ще се опитаме да използваме REST API с пълно марширане и демаркиране на Foo Resource - както с JSON, така и с XML.

5.1. Извличане на ресурсите, които нямат Приемете 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. Извличане на ресурс с приложение / xml Accept Header

Нека сега извлечем изрично Ресурса като XML представяне. Ще определим набор от преобразуватели и ще ги зададем на RestTemplate.

Тъй като консумираме XML, ще използваме същия XStream маршалер като преди:

@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. Извличане на ресурс с заглавието application / json Accept

По същия начин, нека сега консумираме REST API, като поискаме 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. Актуализирайте ресурс с XML тип съдържание

И накрая, нека също изпратим JSON данни към REST API и да посочим типа на носителя на тези данни чрез заглавката Content-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()); }

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.

Както винаги, кодът, представен в тази статия, е достъпен в Github.