由于HAL + JSON媒体类型不清晰,无法使用Spring HATEOAS执行HAL + JSON Level 3 RESTful API

Viv*_*ath 12 java rest spring spring-hateoas hal-json

例如,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后缀未注册,因此违反了RFC6838的4.2.8节.

现在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);
}
Run Code Online (Sandbox Code Playgroud)

这很有效,我正在以适当的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案不起作用 ; 我最终在我的JSON中获得了不符合HAL的链接.这是因为application/vnd.service.entity.v1.hal+json被认可的application/*hal+json.发生这种情况的原因是MimeType,检查媒体类型兼容性,仅识别*+以子类型的有效通配符媒体类型开头的媒体类型(例如,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,这些都是无效的MIME类型按照RFC6838的4.2.8节.

似乎唯一正确的方法是将if +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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是不会污染现有的JSON转换器,但似乎不那么优雅.有谁知道这个正确的解决方案?我完全错了吗?

zer*_*gen 4

虽然这个问题有点老了,但我最近偶然发现了同样的问题,所以我想为这个主题贡献我的 2 美分。

我认为这里的问题是HAL对于JSON的理解。正如您已经指出的那样,所有 HAL 都是 JSON,但并非所有 JSON 都是 HAL。根据我的理解,两者之间的区别在于,HAL 定义了语义/结构的一些约定,比如告诉您在属性后面_links您会找到一些链接,而 JSON 只是定义格式key: [value](如 @zeroflagL 已经提到的)

这就是媒体类型被称为 的原因application/hal+json。它基本上表示它是 JSON 格式的 HAL 样式/语义。application/hal+xml这也是存在媒体类型()的原因。

现在,使用供应商特定的媒体类型,您可以定义自己的语义,因此您可以替换 inhal并且application/hal+json不扩展它。

如果我理解正确的话,您基本上想说您有一个自定义媒体类型,它使用 HAL 样式作为 JSON 格式。(这样,客户端就可以使用一些 HAL 库来轻松解析您的 JSON。)

因此,最后我认为您基本上必须决定是否要区分 JSON 和基于 HAL 的 JSON,以及您的 API 是否应该提供其中之一或两者。

如果您想同时提供两者,则必须定义两种不同的媒体类型vnd.service.entity.v1.hal+jsonAND 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 库来解析响应。


编辑:

要绕过必须单独添加每个供应商特定媒体类型的问题,您可以重写要添加到自定义的媒体类型的isCompatibleWith方法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);
                }
            }
));
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢添加新的媒体类型来支持我的供应商特定的媒体类型。我或多或少得出了相同的结论 - HAL 只是 JSON 之上的附加语义,而使用 HAL 的自定义媒体类型是您自己的语义。我想最终它只是归结为记录媒体类型并预先明确 API 返回的每个资源表示都使用 HAL 语义。在代码方面,提供自定义媒体类型来识别您自己的媒体类型也是有意义的。 (2认同)