1. Обзор
В этой статье описывается , как настроить HttpMessageConverters
в Spring .
Проще говоря, мы можем использовать преобразователи сообщений для маршалинга и демаршаллинга Java-объектов в JSON, XML и т. д. и обратно через HTTP.
2. Основы
2.1. Включить веб-MVC
Для начала веб-приложение необходимо настроить с поддержкой Spring MVC. Удобный и очень настраиваемый способ сделать это — использовать аннотацию @EnableWebMvc
:
@EnableWebMvc
@Configuration
@ComponentScan({ "com.foreach.web" })
public class WebConfig implements WebMvcConfigurer {
// ...
}
Обратите внимание, что этот класс реализует WebMvcConfigurer
, что позволит нам изменить список Http-конвертеров по умолчанию на наш собственный.
2.2. Преобразователи сообщений по умолчанию
По умолчанию включены следующие экземпляры HttpMessageConverter
s:
ByteArrayHttpMessageConverter
— конвертирует байтовые массивыStringHttpMessageConverter
— конвертирует строкиResourceHttpMessageConverter
— преобразуетorg.springframework.core.io.Resource
для любого типа потока октетов.SourceHttpMessageConverter
— преобразуетjavax.xml.transform.Source
FormHttpMessageConverter
— преобразует данные формы в/изMultiValueMap<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 в
/foos
с заголовкомAccept , установленным в
application/json
, чтобы получить все ресурсыFoo
в виде JSON. Контроллер Foo
Spring попадает и возвращает соответствующие сущностиFoo
Java- Затем Spring использует один из преобразователей сообщений Джексона для сортировки объектов в JSON.
Давайте теперь посмотрим на особенности того, как это работает, и как мы можем использовать аннотации @ResponseBody
и @RequestBody
.
3.2. @ResponseBody
@ResponseBody
в методе Controller указывает Spring, что возвращаемое значение метода сериализуется непосредственно в тело HTTP-ответа . Как обсуждалось выше, заголовок « Accept
», указанный клиентом, будет использоваться для выбора соответствующего Http-конвертера для сортировки объекта.
Давайте рассмотрим простой пример :
@GetMapping("/{id}")
public @ResponseBody Foo findById(@PathVariable long id) {
return fooService.findById(id);
}
Теперь клиент укажет заголовок «Accept» в application/json
в запросе — пример команды curl
:
curl --header "Accept: application/json"
http://localhost:8080/spring-boot-rest/foos/1
Класс Фу
:
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"}' 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.foreach.web" })
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(createXmlHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
}
private HttpMessageConverter<Object> 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 был выбранной реализацией маршаллера/демаршаллера, но можно использовать и другие , такие как JibxMarshaller .
На этом этапе — с включенным XML на серверной части — мы можем использовать API с XML-представлениями:
curl --header "Accept: application/xml"
http://localhost:8080/spring-boot-rest/foos/1
4.1. Поддержка весенней загрузки
Если мы используем Spring Boot, мы можем избежать внедрения WebMvcConfigurer
и добавления всех преобразователей сообщений вручную, как мы делали выше.
Мы можем просто определить разные bean-компоненты HttpMessageConverter
в контексте, и Spring Boot автоматически добавит их в автоконфигурацию, которую он создает:
@Bean
public HttpMessageConverter<Object> createXmlHttpMessageConverter() {
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
// ...
return xmlConverter;
}
5. Использование Spring RestTemplate
с преобразователями HTTP-сообщений
Как и на стороне сервера, преобразование HTTP-сообщений можно настроить на стороне клиента в Spring RestTemplate
.
Мы собираемся настроить шаблон с заголовками « Accept
» и « Content-Type », когда это необходимо.
Затем мы попробуем использовать REST API с полным маршалингом и демаршаллингом ресурса Foo
— как с JSON, так и с XML.
5.1. Получение ресурса без заголовка Accept
@Test
public void whenRetrievingAFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
Foo resource = restTemplate.getForObject(URI, Foo.class, "1");
assertThat(resource, notNullValue());
}
5.2. Получение ресурса с заголовком принятия application
/xml
``
Теперь давайте явно извлечем ресурс в виде XML-представления. Мы собираемся определить набор конвертеров и установить их в RestTemplate.
Поскольку мы потребляем XML, мы собираемся использовать тот же маршаллер XStream, что и раньше:
@Test
public void givenConsumingXml_whenReadingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getXmlMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Foo> response =
restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
}
private List<HttpMessageConverter<?>> getXmlMessageConverters() {
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.setAnnotatedClasses(Foo.class);
MarshallingHttpMessageConverter marshallingConverter =
new MarshallingHttpMessageConverter(marshaller);
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(marshallingConverter);
return converters;
}
5.3. Получение ресурса с заголовком принятия application
/json
``
Точно так же давайте теперь воспользуемся REST API, запросив JSON:
@Test
public void givenConsumingJson_whenReadingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getJsonMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<Foo> response =
restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
}
private List<HttpMessageConverter<?>> getJsonMessageConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
return converters;
}
5.4. Обновите ресурс с XML Content-Type
Наконец, давайте также отправим данные JSON в REST API и укажем тип носителя этих данных через заголовок Content-Type :
@Test
public void givenConsumingXml_whenWritingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getJsonAndXmlMessageConverters());
Foo resource = new Foo("jason");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType((MediaType.APPLICATION_XML));
HttpEntity<Foo> entity = new HttpEntity<>(resource, headers);
ResponseEntity<Foo> response =
restTemplate.exchange(URI, HttpMethod.POST, entity, Foo.class);
Foo fooResponse = response.getBody();
assertThat(fooResponse, notNullValue());
assertEquals(resource.getName(), fooResponse.getName());
}
private List<HttpMessageConverter<?>> getJsonAndXmlMessageConverters() {
List<HttpMessageConverter<?>> converters = getJsonMessageConverters();
converters.addAll(getXmlMessageConverters());
return converters;
}
Что здесь интересно, так это то, что мы можем смешивать типы мультимедиа — мы отправляем данные XML, но ждем данные JSON обратно с сервера . Это показывает, насколько мощным на самом деле является механизм преобразования Spring.
6. Заключение
В этом руководстве мы рассмотрели, как Spring MVC позволяет нам указывать и полностью настраивать преобразователи Http-сообщений для автоматического маршаллинга/демаршаллинга объектов Java в XML или JSON и из них . Это, конечно, упрощенное определение, и механизм преобразования сообщений может делать гораздо больше — как мы видим из последнего тестового примера.
Мы также рассмотрели, как использовать тот же мощный механизм с клиентом RestTemplate
, что приводит к полностью безопасному способу использования API.
Как всегда, код, представленный в этой статье, доступен на GitHub .