1. Обзор
В этом руководстве мы узнаем, как настроить HttpMessageConverters в Spring.
Проще говоря, мы можем использовать конвертеры сообщений для преобразования объектов Java в JSON и XML и обратно по протоколу HTTP.
2. Основы
2.1. Включить WebMVC
Для начала необходимо настроить веб-приложение с поддержкой 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.Источник
- FormHttpMessageConverter – преобразует данные формы в/из многозначной карты<Строка, String>
- Jaxb2RootElementHttpMessageConverter – преобразует объекты Java в/из XML (добавляется только в том случае, если в пути к классу присутствует JAXB2)
- MappingJackson2HttpMessageConverter – преобразует JSON (добавляется только в том случае, если в пути к классу присутствует Jackson 2)
- MappingJacksonHttpMessageConverter – преобразует JSON (добавляется только в том случае, если Jackson присутствует в пути к классу)
- AtomFeedHttpMessageConverter – преобразует каналы Atom (добавляется только в том случае, если Rome присутствует в пути к классу)
- RssChannelHttpMessageConverter – преобразует RSS-каналы (добавляется только в том случае, если Rome присутствует в пути к классу)
3. Взаимодействие между клиентом и сервером - только в формате JSON
3.1. Согласование содержания на высоком уровне
Каждая реализация HttpMessageConverter имеет один или несколько связанных MIME-типов.
При получении нового запроса Spring будет использовать заголовок “Accept” для определения типа носителя, на который он должен ответить.
Затем он попытается найти зарегистрированный конвертер, способный обрабатывать этот конкретный тип носителя. Наконец, он будет использовать это для преобразования объекта и отправки ответа.
Процесс аналогичен при получении запроса, содержащего информацию в формате JSON. Платформа будет использовать заголовок “Content-Type” для определения типа носителя в теле запроса.
Затем он выполнит поиск HttpMessageConverter, который может преобразовать текст, отправленный клиентом, в объект Java.
Давайте проясним это на коротком примере:
- Клиент отправляет запрос GET в /foods с заголовком Accept, установленным в application/json, чтобы получить все пищевые ресурсы в формате JSON.
- Задействован контроллер Foo Spring, который возвращает соответствующие объекты Foo Java.
- Затем Spring использует один из конвертеров сообщений Jackson для преобразования сущностей в JSON.
Теперь давайте рассмотрим особенности того, как это работает, и как мы можем использовать аннотации @ResponseBody и @RequestBody.
3.2. @ResponseBody
@ResponseBody в методе контроллера указывает 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
Класс 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"}' http://localhost:8080/spring-boot-rest/foos/1
Мы получим ответ в размере 200 "ОК", что означает успешный ответ:
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<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 в classpath.
Также имейте в виду, что, расширяя этот класс поддержки, мы теряем конвертеры сообщений по умолчанию, которые были предварительно зарегистрированы ранее.
Конечно, теперь мы можем сделать то же самое для Jackson, определив наш собственный 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 и добавления всех конвертеров сообщений вручную, как мы делали выше.
Мы можем просто определить различные компоненты HttpMessageConverter в контексте, и Spring Boot автоматически добавит их в созданную им автоконфигурацию:
@Bean
public HttpMessageConverter<Object> createXmlHttpMessageConverter() {
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
// ...
return xmlConverter;
}
5. Использование RestTemplate от Spring С конвертерами 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 Accept
Теперь давайте явно получим ресурс в виде XML-представления. Мы определим набор преобразователей и установим их в RestTemplate.
Поскольку мы используем XML, мы будем использовать тот же XStream marshaller, что и раньше:
@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 Accept
Аналогично, давайте теперь воспользуемся 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 позволяет нам указывать и полностью настраивать HttpMessageConverters для автоматического преобразования/отмены преобразования объектов Java в XML или JSON и из них. Это, конечно, упрощенное определение, и механизм преобразования сообщений может сделать гораздо больше, как мы можем видеть из последнего тестового примера.
Оригинал статьи: https://www.baeldung.com/spring-httpmessageconverter-rest