由于HAL+JSON mediatype不够清晰,java无法使用Spring HATEOAS实现HAL+JSON级别3 RESTful API
Level 3RESTful 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无法识别我的定制媒体类型。因此,我必须弄清楚如何让它知道,它需要对application/vnd.service.entity.v1.hal+json
形式的媒体类型使用HAL对象映射器
在我的第一次尝试中,我在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
不是application/*hal+json
识别的。发生这种情况的原因是MimeType
检查媒体类型兼容性,只会将以*+
开头的媒体类型识别为子类型(例如application/*+json
)的有效通配符媒体类型。这就是第一个解决方案起作用的原因(巧合)
所以这里有两个问题:
MimeType
将永远不会根据{}识别{ }形式的特定于供应商的HAL媒体类型李> MimeType
将{}形式的供应商特定HAL媒体类型识别为 application/*+hal+json
,然而根据section 4.2.8 of RFC6838这些是无效的模拟类型李>
似乎只有+hal
被识别为有效后缀时才是正确的,在这种情况下,上面的第二个选项就可以了。否则,任何其他类型的通配符介质类型都无法专门识别特定于供应商的HAL介质类型。唯一的选择是用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", "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转换器,但似乎不够优雅。有人知道解决这个问题的正确方法吗?我是不是完全错了
# 1 楼答案
虽然这个问题有点老了,但我最近偶然发现了同样的问题,所以我想在这个话题上花2美分
我认为这里的问题在于HAL对JSON的理解。正如您已经指出的here,所有的HAL都是JSON,但并非所有的JSON都是HAL。根据我的理解,两者之间的区别在于,HAL定义了语义/结构的一些约定,比如告诉你,在像
_links
这样的属性后面,你会找到一些链接,而JSON只定义了像key: [value]
这样的格式(正如@zeroplagl已经提到的)这就是为什么媒体类型被称为
application/hal+json
的原因。它基本上说是JSON格式的HAL风格/语义。这也是存在媒体类型application/hal+xml
(source )的原因现在,使用特定于供应商的媒体类型,您可以定义自己的语义,从而替换
application/hal+json
中的hal
,而不扩展它如果我没弄错的话,你基本上想说,你有一个自定义的媒体类型,它使用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媒体类型(例如,通过添加一个自定义的
MappingJackson2HttpMessageConverter
来支持+json
媒体类型并使用前面提到的_halObjectMapper
),因此对application/vnd.service.entity.v1+json
的每个请求都由返回基于HAL的JSON的转换器处理从我的观点来看,我认为正确的方法是只区分JSON和其他格式,比如XML,在您的媒体类型文档中,您会说,您的JSON受HAL启发,客户可以使用HAL LIB解析响应。
编辑:
要绕过必须分别添加每个特定于供应商的介质类型的问题,可以覆盖要添加到自定义
MappingJackson2HttpMessageConverter
的介质类型的isCompatibleWith方法