Spring HATEOAS 客户端忽略空链接 - "Template must not be null or empty!" 异常

Spring HATEOAS client ignore null links - "Template must not be null or empty!" exception

我正在访问第三方提供的 HATEOAS API,出于某种原因,我们从他们那里收到的响应包含 href 为空值的链接。这会引发异常。我无法更改响应,因为我无法控制此 API。有什么办法解决这个问题吗?

下面是 JSON 的示例:

{
    "_embedded": {
        "example": [{ ... }]
    }
    "_links": {
        "next": {
            "href": null
        },
        "prev": {
            "href": null
        },
        "self": {
            "href": "https://bag.basisregistraties.overheid.nl/api/v1/panden"
        }
    }
}

当我使用 RestTemplate 发出请求时,我收到 IllegalArgumentException 消息“Template must not be null or empty!”

Caused by: java.lang.IllegalArgumentException: Template must not be null or empty!
    at org.springframework.util.Assert.hasText(Assert.java:284)
    at org.springframework.hateoas.UriTemplate.<init>(UriTemplate.java:56)
    at org.springframework.hateoas.Link.<init>(Link.java:94)
    at org.springframework.hateoas.hal.Jackson2HalModule$HalLinkListDeserializer.deserialize(Jackson2HalModule.java:583)
    at org.springframework.hateoas.hal.Jackson2HalModule$HalLinkListDeserializer.deserialize(Jackson2HalModule.java:528)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369)

我尝试在本地提供这个 JSON,这样我就可以从中删除下一个和上一个链接,而且效果很好。我查看了代码,但它失败了,因为它正在尝试执行不接受 null 作为参数的 new UriTemplate(href)

我们是 运行 Spring 引导版本 2.1.8.RELEASE 和 spring-hateoas 版本 0.25.2。下面是发出请求的代码:

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
    Jackson2HalModule jackson2HalModule = new Jackson2HalModule();
    mapper.registerModule(jackson2HalModule);

    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(MediaType.parseMediaTypes(Arrays.asList("application/json;charset=UTF-8", "application/hal+json")));
    converter.setObjectMapper(mapper);

    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();

    RestTemplate restTemplate = restTemplateBuilder.messageConverters(Collections.singletonList(converter)).build();

    HttpHeaders headers = new HttpHeaders();
    headers.set("Accept", "application/json, application/*+json");
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

    HttpEntity<String> entity = new HttpEntity(entity.toString(), headers);

    ParameterizedTypeReference<Resources<Panden>> typeRefDevices = new ParameterizedTypeReference<>() {};
    ResponseEntity<Resources<Panden>> result = restTemplate.exchange(endpoint, HttpMethod.POST, entity, typeRefDevices);;

在花了一些时间查看 Jackson2HalModule 的代码后,我找到了解决这个问题的方法。没有可用于告诉库简单地忽略具有空值的 link 的配置。我可以做的是重写 deserialize() 方法,这样我就可以添加一个空检查并避免这个问题。

为了做到这一点,我首先必须创建新的 link 解串器 class。它需要扩展 Jackson2HalModule.HalLinkListDeserializer 然后我只需从库源代码中复制并粘贴代码并进行我想要的修改。

public class CustomHalLinkListDeserializer extends Jackson2HalModule.HalLinkListDeserializer {

    @Override
    public List<Link> deserialize(JsonParser jp, DeserializationContext ctxt) 
                                      throws IOException, JsonProcessingException {

        List<Link> result = new ArrayList<Link>();
        String relation;

        // links is an object, so we parse till we find its end.
        while (!JsonToken.END_OBJECT.equals(jp.nextToken())) {

            if (!JsonToken.FIELD_NAME.equals(jp.getCurrentToken())) {
                throw new JsonParseException(jp, 
                              "Expected relation name", jp.getCurrentLocation());
            }

            // save the relation in case the link does not contain it
            relation = jp.getText();

            if (JsonToken.START_ARRAY.equals(jp.nextToken())) {
                while (!JsonToken.END_ARRAY.equals(jp.nextToken())) {
                    CustomLink link = jp.readValueAs(CustomLink.class);
                    String href = link.getHref() != null ? link.getHref() : "http://dummy";
                    result.add(new Link(href, relation));
                }
            } else {
                CustomLink link = jp.readValueAs(CustomLink.class);
                String href = link.getHref() != null ? link.getHref() : "http://dummy";
                result.add(new Link(href, relation));
            }
        }

        return result;

    }
}

然后,为了告诉 Jackson2HalModule 使用新的 class,我必须创建自己的 MixInAnnotation class,其中我指定使用 [=21] 反序列化 link =]CustomHalLinkListDeserializer

public abstract class CustomResourceSupportMixin extends ResourceSupport {

    @Override
    @XmlElement(name = "link")
    @JsonProperty("_links")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class)
    @JsonDeserialize(using = CustomHalLinkListDeserializer.class)
    public abstract List<Link> getLinks();

}

新的 MixInAnnotation 可以在配置 jackson2HalModule 时设置如下

ObjectMapper mapper = new ObjectMapper();
Jackson2HalModule jackson2HalModule = new Jackson2HalModule();
jackson2HalModule.setMixInAnnotation(ResourceSupport.class,
                                         CustomResourceSupportMixin.class);
mapper.registerModule(jackson2HalModule);