Java 无法执行HAL+;JSON级别3 RESTful API,带有Spring HATEOAS,这是因为HAL+;JSON媒体类型

Java 无法执行HAL+;JSON级别3 RESTful API,带有Spring HATEOAS,这是因为HAL+;JSON媒体类型,java,spring,rest,spring-hateoas,hal-json,Java,Spring,Rest,Spring Hateoas,Hal Json,RESTful API的特性是自定义媒体类型,例如application/vnd.service.entity.v1+json。在我的例子中,我使用提供JSON中相关资源之间的链接 我不清楚使用HAL+JSON的自定义媒体类型的正确格式。我目前拥有的是application/vnd.service.entity.v1.hal+json。我最初使用的是application/vnd.service.entity.v1+hal+json,但是+hal后缀没有注册,因此违反了规则 现在Spring H

RESTful API的特性是自定义媒体类型,例如
application/vnd.service.entity.v1+json
。在我的例子中,我使用提供JSON中相关资源之间的链接

我不清楚使用HAL+JSON的自定义媒体类型的正确格式。我目前拥有的是
application/vnd.service.entity.v1.hal+json
。我最初使用的是
application/vnd.service.entity.v1+hal+json
,但是
+hal
后缀没有注册,因此违反了规则

现在Spring HATEOAS支持JSON中的现成链接,但对于HAL-JSON,您需要使用
@EnableHypermediaSupport(type=EnableHypermediaSupport.HypermediaType.HAL)
。在我的例子中,因为我使用的是Spring Boot,所以我将其附加到我的初始值设定项类(即扩展
SpringBootServletilizer
)中。但是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格式的链接。然而,这是巧合。这是因为最终报告为与
应用程序/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
    永远不会根据
    application/*HAL+json
    识别形式为
    application/vnd.service.entity.v1.HAL+json
    的供应商特定的HAL媒体类型
  • MimeType
    将根据
    application/*+HAL+json
    识别供应商特定的格式为
    application/vnd.service.entity.v1+HAL+json
    的HAL媒体类型,但是这些类型是无效的MimeType
如果将
+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美分

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

这就是为什么媒体类型被称为
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
vnd.service.entity.v1+json
。对于
vnd.service.entity.v1.hal+json
媒体类型,您必须添加自定义的
MappingJackson2HttpMessageConverter
,它使用
\u halObjectMapper
返回基于hal的json,而默认情况下支持
+json
媒体类型以旧的json格式返回资源

如果您始终希望提供基于HAL的JSON,则必须启用HAL作为默认的JSON媒体类型(例如,通过添加一个自定义的
MappingJackson2HttpMessageConverter
,它支持
+JSON
媒体类型,并使用前面提到的
\u HaloObject Mapper
),因此对
应用程序/v的每个请求
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);
                }
            }
));