Level 3 RESTful API的功能包括自定义媒体类型,例如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
后缀未注册,因此违反了section 4.2.8 of 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 Boot初始化程序中:
@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
(见后面的解释)。我不喜欢这个解决方案,因为它正在用HAL问题污染现有的JSON转换器。所以,我做了一个不同的解决方案:
@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
<{1}} 未被<{1}}识别。发生这种情况的原因是application/*hal+json
检查媒体类型兼容性,只识别以MimeType
开头的媒体类型作为子类型的有效通配符媒体类型(例如{{1} })。这就是第一个解决方案起作用的原因(巧合)。
所以这里有两个问题:
*+
永远不会识别针对application/*+json
的{{1}}格式的特定于供应商的HAL媒体类型。 MimeType
将识别针对application/vnd.service.entity.v1.hal+json
的格式为application/*hal+json
的特定于供应商的HAL媒体类型,然而这些类型无效mimetypes按section 4.2.8 of RFC6838。似乎唯一的正确方式是将MimeType
识别为有效后缀,在这种情况下,上面的第二个选项就可以了。否则,任何其他类型的通配卡媒体类型都无法专门识别供应商特定的HAL媒体类型。唯一的选择是覆盖现有的带有HAL问题的JSON消息转换器(参见第一个解决方案)。
现在,另一种解决方法是在创建邮件转换器支持的媒体类型列表时指定您正在使用的每个自定义媒体类型。那就是:
application/vnd.service.entity.v1+hal+json
这有利于不污染现有的JSON转换器,但似乎不太优雅。有谁知道这个正确的解决方案?我完全错了吗?
答案 0 :(得分:4)
虽然这个问题有点陈旧,但我最近偶然发现了同样的问题,所以我想给这个话题2美分。
我认为这里的问题是关于JSON的HAL理解。正如您已经指出here,所有HAL都是JSON,但并非所有JSON都是HAL。根据我的理解,两者之间的区别在于HAL为语义/结构定义了一些约定,比如告诉你在_links
之类的属性后面你会找到一些链接,而JSON只是定义像{{key: [value]
这样的格式。 1}}(正如@zeroflagL已经提到的那样)
这就是为什么媒体类型被称为application/hal+json
的原因。它基本上说它是JSON格式的HAL样式/语义。这也是存在媒体类型application/hal+xml
(source )的原因。
现在使用特定于供应商的媒体类型,您可以定义自己的语义,因此替换hal
中的application/hal+json
并且不对其进行扩展。
如果我理解你的话,你基本上想要说你有一个自定义媒体类型,它使用HAL样式进行JSON格式化。 (这样,客户端可以使用一些HAL库来轻松解析您的JSON。)
所以,最后我认为您基本上必须决定是否要区分JSON和基于HAL的JSON,并且您的API应该提供其中一个或两者。
如果您想同时提供这两种媒体类型,则必须定义两种不同的媒体类型vnd.service.entity.v1.hal+json
和vnd.service.entity.v1+json
。对于vnd.service.entity.v1.hal+json
媒体类型,您必须添加使用MappingJackson2HttpMessageConverter
的自定义_halObjectMapper
以返回基于HAL的JSON,而默认情况下支持+json
媒体类型返回旧JSON的资源。
如果您始终希望提供基于HAL的JSON,则必须启用HAL作为默认的JSON-Media类型(例如,通过添加支持MappingJackson2HttpMessageConverter
媒体类型的自定义+json
和使用前面提到的_halObjectMapper
,因此每个application/vnd.service.entity.v1+json
的请求都由此转换器处理,返回基于HAL的JSON。
从我的观点来看,我认为正确的方法是只区分JSON和其他格式(如XML)和媒体类型文档,你会说,你的JSON是以某种方式受到HAL启发的客户端可以使用HAL库来解析响应。
修改强>
要避免您必须单独添加每个供应商特定媒体类型的问题,您可以覆盖要添加到自定义MappingJackson2HttpMessageConverter
converter.setSupportedMediaTypes(Arrays.asList(
new MediaType("application", "doesntmatter") {
@Override
public boolean isCompatibleWith(final MediaType other) {
if (other == null) {
return false;
}
else if (other.getSubtype().startsWith("vnd.") && other.getSubtype().endsWith("+json")) {
return true;
}
return super.isCompatibleWith(other);
}
}
));