繁体   English   中英

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

[英]Cannot do HAL+JSON Level 3 RESTful API with Spring HATEOAS due to lack of clarity surrounding HAL+JSON media-type

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);
}

这很有效,我正在以适当的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 无法识别application/vnd.service.entity.v1.hal+json application/*hal+json 发生这种情况的原因是,检查媒体类型兼容性的MimeType仅识别以*+开头的媒体类型作为子类型的有效通配符媒体类型(例如, application/*+json )。 这就是第一个解决方案起作用的原因(巧合)。

所以这里有两个问题:

  • MimeType 永远不会识别表单application/vnd.service.entity.v1.hal+jsonapplication/*hal+json的特定于供应商的HAL媒体类型。
  • MimeType 识别针对application/*+hal+json的表单application/vnd.service.entity.v1+hal+json的特定于供应商的HAL媒体类型, 但是这些是根据RFC6838的4.2.8部分的无效mimetypes。

似乎唯一正确的方法是将+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转换器,但似乎不那么优雅。 有谁知道这个正确的解决方案? 我完全错了吗?

虽然这个问题有点陈旧,但我最近偶然发现了同样的问题,所以我想给这个话题2分钱。

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

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

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

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

所以,最后我认为你基本上必须决定你想要区分JSON和基于HAL的JSON,而你的API应该提供其中之一或两者。

如果要同时提供这两种媒体类型,则必须定义两种不同的媒体类型: vnd.service.entity.v1.hal+json AND vnd.service.entity.v1+json 对于vnd.service.entity.v1.hal+json媒体类型,您必须添加自定义的MappingJackson2HttpMessageConverter ,它使用_halObjectMapper返回基于HAL的JSON,而默认情况下支持+json媒体类型,以旧的JSON _halObjectMapper返回您的资源。

如果您总是希望提供基于HAL的JSON,则必须启用HAL作为默认的JSON-Media类型(例如,通过添加支持+json媒体类型并使用之前提到的_halObjectMapper的自定义MappingJackson2HttpMessageConverter ),因此每个请求application/vnd.service.entity.v1+json由此转换器处理,返回基于HAL的JSON。

从我的观点来看,我认为正确的方法是仅区分JSON和其他格式(如XML)和您所说的媒体类型文档,您的JSON受HAL启发的方式使客户端可以使用HAL库来解析响应。


编辑:

要绕过您必须单独添加每个供应商特定媒体类型的问题,您可以覆盖要添加到自定义MappingJackson2HttpMessageConverter的媒体类型的isCompatibleWith方法

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);
                }
            }
));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM