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

Tag*_*a85 3 java spring jackson spring-boot

我正在使用 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);
    }
Run Code Online (Sandbox Code Playgroud)

这以以下形式返回:

{
  "_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",
        "id" : "cfae1a04-cb00-4ad4-b4e8-6971eff64807",
        "avatarUrl" : "user-16",
        "_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",
          "templated" : true
        },
        "makers" : {
          "href" : "http://some-api-link"
        },
        "originalMaker" : {
          "href" : "http://some-api-link"
        }
      }
    } ]
  }
}
Run Code Online (Sandbox Code Playgroud)

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

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValueAsString(controller.listMyData(id)); // the same function as above
Run Code Online (Sandbox Code Playgroud)

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

"{content: [...], _links: []}"
Run Code Online (Sandbox Code Playgroud)

因此,当我从带有缓存内容的 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;
    }
}

Run Code Online (Sandbox Code Playgroud)

谢谢你。

jcc*_*ero 6

原因是当 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));

Run Code Online (Sandbox Code Playgroud)