Уровень 3 API RESTful использует специальные типы мультимедиа, например application/vnd.service.entity.v1+json
. В моем случае я использую HAL для предоставления ссылок между связанными ресурсами в моем JSON.
Я не совсем понял правильный формат для специального медиа-типа, который использует HAL + JSON. То, что у меня сейчас, выглядит как application/vnd.service.entity.v1.hal+json
. Сначала я пошел с application/vnd.service.entity.v1+hal+json
, но суффикс +hal
не зарегистрирован и поэтому нарушает раздел 4.2.8 RFC6838.
Теперь Spring HATEOAS поддерживает ссылки в JSON из коробки, но специально для HAL-JSON вам нужно использовать @EnableHypermediaSupport(type=EnableHypermediaSupport.HypermediaType.HAL)
. В моем случае, поскольку я использую Spring Boot, я присоединяю это к моему классу инициализатора (т.е. К тому, который расширяет SpringBootServletInitializer
). Но Spring Boot не будет распознавать мои собственные медиа-типы из коробки. Поэтому для этого мне нужно было выяснить, как сообщить ему, что ему нужно использовать объект-сопоставление HAL для медиа-типов формы application/vnd.service.entity.v1.hal+json
.
Для моей первой попытки я добавил следующее в мой инициализатор загрузки Spring:
@Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(
new MediaType("application", "json", Charset.defaultCharset()),
new MediaType("application", "*+json", Charset.defaultCharset()),
new MediaType("application", "hal+json"),
new MediaType("application", "*hal+json")
));
CurieProvider curieProvider = getCurieProvider(beanFactory);
RelProvider relProvider = beanFactory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
halObjectMapper.registerModule(new Jackson2HalModule());
halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider));
converter.setObjectMapper(halObjectMapper);
return new HttpMessageConverters(converter);
}
Это сработало, и я возвращал ссылки в правильном формате HAL. Однако это было совпадение. Это связано с тем, что фактический тип носителя, который заканчивается сообщением как "совместимый" с application/vnd.service.entity.v1.hal+json
, равен *+json
; он не распознает его против application/*hal+json
(см. ниже для объяснения). Мне не понравилось это решение, поскольку оно загрязняло существующий JSON-конвертер проблемами HAL. Итак, я сделал другое решение:
@Configuration
public class ApplicationConfiguration {
private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";
@Autowired
private BeanFactory beanFactory;
@Bean
public HttpMessageConverters customConverters() {
return new HttpMessageConverters(new HalMappingJackson2HttpMessageConverter());
}
private class HalMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
public HalMappingJackson2HttpMessageConverter() {
setSupportedMediaTypes(Arrays.asList(
new MediaType("application", "hal+json"),
new MediaType("application", "*hal+json")
));
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
setObjectMapper(halObjectMapper);
}
}
}
Это решение не работает; Я получаю ссылки в моем JSON, которые не соответствуют HAL. Это связано с тем, что application/vnd.service.entity.v1.hal+json
не распознается application/*hal+json
. Причина этого в том, что MimeType
, который проверяет совместимость медиа-типа, распознает только типы медиа, которые начинаются с *+
как допустимые типы медиафайлов wild-card для подтипов (например, application/*+json
). Вот почему первое решение сработало (по совпадению).
Итак, здесь есть две проблемы:
-
MimeType
будет никогда распознавать типы медиафайлов HAL формыapplication/vnd.service.entity.v1.hal+json
дляapplication/*hal+json
. -
MimeType
будет распознавать типы медиафайлов HAL, специфичные для поставщика формыapplication/vnd.service.entity.v1+hal+json
по сравнению сapplication/*+hal+json
, однако, это недопустимые типы mimetypes по раздел 4.2.8 RFC6838.
Похоже, что единственный правильный способ был бы, если +hal
распознается как действительный суффикс, и в этом случае второй вариант выше будет в порядке. В противном случае какой-либо другой вид медиа-типа wild-card не может распознавать специфические для HAL медиа-типы конкретного поставщика. Единственным вариантом было бы переопределить существующий конвертер сообщений JSON с проблемами HAL (см. Первое решение).
Другим обходным решением в настоящее время будет указание каждого пользовательского типа мультимедиа, который вы используете, при создании списка поддерживаемых типов медиа для конвертера сообщений. То есть:
@Configuration
public class ApplicationConfiguration {
private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";
@Autowired
private BeanFactory beanFactory;
@Bean
public HttpMessageConverters customConverters() {
return new HttpMessageConverters(new HalMappingJackson2HttpMessageConverter());
}
private class HalMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
public HalMappingJackson2HttpMessageConverter() {
setSupportedMediaTypes(Arrays.asList(
new MediaType("application", "hal+json"),
new MediaType("application", "vnd.service.entity.v1.hal+json"),
new MediaType("application", "vnd.service.another-entity.v1.hal+json"),
new MediaType("application", "vnd.service.one-more-entity.v1.hal+json")
));
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
setObjectMapper(halObjectMapper);
}
}
}
Это имеет смысл не загрязнять существующий конвертер JSON, но кажется менее элегантным. Кто-нибудь знает правильное решение для этого? Неужели я об этом совершенно не так?