为什么ResponseBody和Jackson ObjectMapper不返回相同的输出?

问题描述

我正在使用Spring Boot应用程序。

我的控制器中有一个返回一些资源的方法

    @ResponseBody
    @Transactional(rollbackFor = Exception.class)
    @GetMapping(value="data/{itemId}/items",produces="application/json")
    public Resources<DataExcerpt> listMyData(@PathVariable("debateId") UUID debateId)){

       List<DataExcerpt> dataExcerpts = dataService
                .listMyData(id)
                .stream()
                .map(d -> this.projectionFactory.createProjection(DataExcerpt.class,d))
                .collect(Collectors.toList());
        return new Resources<>(dataExcerpts);
    }

这将以以下形式返回内容

{
  "_embedded" : {
    "items" : [ {
      "position" : {
        "name" : "Oui","id" : "325cd3b7-1666-4c44-a55f-1e7cc936a3aa","color" : "#51B63D","usedForPositionType" : "FOR_CON"
      },"id" : "5aa48cfb-5505-43b6-b0a9-5481c895e2bf","item" : [ {
        "index" : 0,"id" : "43c2dcd0-6bdb-43b0-be97-2a40b99bc753","description" : {
          "id" : "021ad7cd-4bf1-4dce-9ea7-10980440a049","title" : "Item description","modificationCount" : 0
        }
      } ],"title" : "Item title","originalMaker" : {
        "username" : "jeremieca","id" : "cfae1a04-cb00-4ad4-b4e8-6971eff64807","avatarUrl" : "user-16","_links" : {
          "self" : {
            "href" : "http://some-api-link"
          }
        }
      },"itemState" : {
        "itemState" : "LIVE",},"opinionImprovements" : [ ],"sourcesJson" : [ ],"makers" : [ {
        "username" : "jeremieca","_links" : {
          "self" : {
            "href" : "http://some-api-link"
          }
        }
      } ],"modificationsCounter" : 1,"originalBuyer" : "fd9b68f9-7c0c-4120-869c-c63d1680e7f0","updateTrace" : {
        "createdOn" : "2020-05-25T08:12:56.846+0000","createdBy" : "cfae1a04-cb00-4ad4-b4e8-6971eff64807","updatedOn" : "2020-05-25T08:12:56.845+0000","updatedBy" : "cfae1a04-cb00-4ad4-b4e8-6971eff64807"
      },"_links" : {
        "self" : {
          "href" : "some-api-link","templated" : true
        },"newEditions" : {
          "href" : "some-api-link","makers" : {
          "href" : "http://some-api-link"
        },"originalMaker" : {
          "href" : "http://some-api-link"
        }
      }
    } ]
  }
}

另一方面,我也想在Redis中缓存这些答案,以避免每次都运行整个过程。为此,我正在使用Jackson的ObjectMapper将我的资源转换为字符串

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValueAsstring(controller.listMyData(id)); // the same function as above

writeValueAsstring 输出的结构与第一个不同:

"{content: [...],_links: []}"

因此,当我从具有缓存内容的API返回时,其结构与控制器在没有缓存的情况下向我发送的结构不同。

那是为什么? Jackson是否无法正确将Resources Hateoas结构写为字符串? 我想念什么吗?

编辑

这是Resources.class:

package org.springframework.hateoas;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.util.Assert;

@XmlRootElement(name = "entities")
public class Resources<T> extends ResourceSupport implements Iterable<T> {
    private final Collection<T> content;

    protected Resources() {
        this(new ArrayList(),(Link[])());
    }

    public Resources(Iterable<T> content,Link... links) {
        this(content,(Iterable) Arrays.asList(links));
    }

    public Resources(Iterable<T> content,Iterable<Link> links) {
        Assert.notNull(content,"Content must not be null!");
        this.content = new ArrayList();
        Iterator var3 = content.iterator();

        while (var3.hasNext()) {
            T element = var3.next();
            this.content.add(element);
        }

        this.add(links);
    }

    public static <T extends Resource<S>,S> Resources<T> wrap(Iterable<S> content) {
        Assert.notNull(content,"Content must not be null!");
        ArrayList<T> resources = new ArrayList();
        Iterator var2 = content.iterator();

        while (var2.hasNext()) {
            S element = var2.next();
            resources.add(new Resource(element,new Link[0]));
        }

        return new Resources(resources,new Link[0]);
    }

    @XmlAnyElement
    @XmlElementWrapper
    @JsonProperty("content")
    public Collection<T> getContent() {
        return Collections.unmodifiableCollection(this.content);
    }

    public Iterator<T> iterator() {
        return this.content.iterator();
    }

    public String toString() {
        return String.format("Resources { content: %s,%s }",this.getContent(),super.toString());
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (obj != null && obj.getClass().equals(this.getClass())) {
            Resources<?> that = (Resources) obj;
            boolean contentEqual = this.content == null ? that.content == null : this.content.equals(that.content);
            return contentEqual ? super.equals(obj) : false;
        } else {
            return false;
        }
    }

    public int hashCode() {
        int result = super.hashCode();
        result += this.content == null ? 0 : 17 * this.content.hashCode();
        return result;
    }
}

谢谢。

解决方法

原因是,当Spring使用HATEOAS配置MVC或Spring Boot应用程序时,它将配置自定义的Jackson模块,以处理Resources类以及对象的其余部分的序列化和反序列化过程API公开的模型。

如果要获得类似的结果,可以执行以下操作:

import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;

// ...

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.writeValueAsString(controller.listMyData(id));

,

我的建议是提供扩展hateoas'ResourceSupport的POJO,(反序列化)将通过POJO进行。

ResourcesJson(根元素)

public class ResourcesJson extends ResourceSupport {
    @JsonProperty("_embedded")
    private ResourcesEmbeddedListJson  embedded;

    //getters and setters
}

嵌入“包装器”

public class ResourcesEmbeddedListJson extends ResourceSupport {
    private Collection<T> content;

    //getters and setters
}

或者,如果您不想让它变得丑陋,可以使用此org.springframework.hateoas.client.Traverson组件。