Рубрики
Без рубрики

Конвертеры Http-сообщений с фреймворком Spring

Как настроить HttpMessageConverters для REST API с Spring и как использовать эти конвертеры с RestTemplate.

Автор оригинала: baeldung.

1. Обзор

В этой статье описывается как настроить HttpMessageConverters весной .

Проще говоря, мы можем использовать конвертеры сообщений для маршалла и немаршалла объектов Java в JSON, XML и т. Д. И из них – по HTTP.

Дальнейшее чтение:

Согласование содержимого Spring MVC

Возврат данных изображения/мультимедиа с помощью Spring MVC

Двоичные форматы данных в API Spring REST

2. Основы

2.1. EnableWebMvc

Для начала веб-приложение должно быть настроено с поддержкой Spring MVC. Удобный и очень настраиваемый способ сделать это-использовать аннотацию @EnableWebMvc :

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

Обратите внимание, что этот класс реализует WebMvcConfigurer – который позволит нам изменить список Http-конвертеров по умолчанию с помощью нашего собственного.

2.2. Конвертеры Сообщений По Умолчанию

По умолчанию используется следующее Экземпляры HttpMessageConverter s предварительно включены:

  • ByteArrayHttpMessageConverter – преобразует массивы байтов
  • StringHttpMessageConverter – преобразует строки
  • ResourceHttpMessageConverter – преобразует org.springframework.core.io.Resource для любого типа потока октетов
  • SourceHttpMessageConverter – преобразует javax.xml.transform.Источник
  • FormHttpMessageConverter – преобразует данные формы в/из многозначной карты String> . String>
  • . Jaxb2RootElementHttpMessageConverter
  • – преобразует объекты Java в/из XML (добавляется только в том случае, если JAXB2 присутствует в пути к классу) MappingJackson2HttpMessageConverter
  • – преобразует JSON (добавляется только в том случае, если Jackson 2 присутствует в пути к классу) MappingJacksonHttpMessageConverter
  • – преобразует JSON (добавляется только в том случае, если Джексон присутствует в пути к классу) AtomFeedHttpMessageConverter
  • – преобразует каналы Atom (добавляется только в том случае, если в пути к классу присутствует Rome) RssChannelHttpMessageConverter – преобразует RSS-каналы

3. Связь Клиент-Сервер – Только JSON

3.1. Согласование Контента На Высоком Уровне

Каждая реализация HttpMessageConverter имеет один или несколько связанных типов MIME.

При получении нового запроса Spring будет использовать заголовок “ Accept “, чтобы определить тип носителя, на который он должен ответить .

Затем он попытается найти зарегистрированный конвертер, способный обрабатывать этот конкретный тип носителя. Наконец, он будет использовать это для преобразования сущности и отправки ответа.

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

Затем он будет искать HttpMessageConverter , который может преобразовать тело, отправленное клиентом, в объект Java.

Давайте проясним это с помощью краткого примера:

  • клиент отправляет запрос GET в /foo с заголовком Accept , установленным в application/json – для получения всех Foo ресурсов в формате JSON
  • контроллер Foo Spring нажат и возвращает соответствующие объекты Foo Java
  • Затем Spring использует один из преобразователей сообщений Джексона для преобразования сущностей в JSON

Давайте теперь рассмотрим особенности того, как это работает – и как мы можем использовать аннотации @ResponseBody и @ RequestBody .

3.2. @ResponseBody

@ResponseBody в методе контроллера указывает Spring, что возвращаемое значение метода сериализуется непосредственно в тело HTTP-ответа . Как обсуждалось выше, заголовок ” Accept “, указанный Клиентом, будет использоваться для выбора соответствующего Http-конвертера для управления сущностью.

Давайте рассмотрим простой пример :

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

Теперь клиент укажет заголовок “Принять” в application/json в команде request – example curl :

curl --header "Accept: application/json" 
  http://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 аннотацию в аргументе метода контроллера, чтобы указать , что тело 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"}' http://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 был выбран реализацией marshaller/unmarshaller, но другие как CastorMarshaller также могут быть использованы.

На этом этапе – с включенным XML на задней панели – мы можем использовать API с XML-представлениями:

curl --header "Accept: application/xml" 
  http://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. Использование RestTemplate Spring С Преобразователями Http-Сообщений

Как и на стороне сервера, преобразование Http-сообщений может быть настроено на стороне клиента на Spring RestTemplate .

Мы собираемся настроить шаблон с заголовками ” Accept ” и ” Content-Type “, когда это необходимо. Затем мы попытаемся использовать REST API с полным маршалингом и безмаршалингом ресурса Food – как с JSON, так и с XML.

5.1. Получение Ресурса Без заголовка Accept

@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. Получение Ресурса С Заголовком application/xml Accept

Теперь давайте явно извлекем ресурс в виде XML-представления. Мы определим набор преобразователей и установим их на RestTemplate.

Поскольку мы потребляем XML, мы будем использовать тот же XStreammarshaller, что и раньше:

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

Что интересно здесь, так это то, что мы можем смешивать типы носителей – мы отправляем XML-данные, но ждем данных JSON с сервера . Это показывает, насколько мощным на самом деле является механизм преобразования пружины.

6. Заключение

В этом уроке мы рассмотрели, как Spring MVC позволяет нам указывать и полностью настраивать HttpMessageConverters для автоматического маршалла/удаления объектов Java в XML или JSON и из них . Это, конечно, упрощенное определение, и механизм преобразования сообщений может сделать гораздо больше, как мы видим из последнего тестового примера.

Мы также рассмотрели, как использовать тот же мощный механизм с RestTemplate клиентом, что приведет к полностью типобезопасному способу использования API.

Как всегда, код, представленный в этой статье, доступен на Github .