使用Spring RestTemplate处理分页API

45

我们的 REST API 返回分页结果。这是一个控制器的示例。

@RequestMapping(value = "/search", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8")
@ResponseStatus(HttpStatus.OK)
public Page<MyObject> findAll(Pageable pageable) {
  ...
}

有没有一种使用RestTemplate轻松消费该API的方法?

如果我们这样做

ParameterizedTypeReference<Page<MyObject>> responseType = new ParameterizedTypeReference<Page<MyObject>>() { };

ResponseEntity<Page<MyObject>> result = restTemplate.exchange(url, HttpMethod.GET, null/*httpEntity*/, responseType);

List<MyObject> searchResult = result.getBody().getContent();

它抛出一个异常

org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not construct instance of org.springframework.data.domain.Page, 
problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information at [Source: java.io.PushbackInputStream@3be1e1f2; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.data.domain.Page, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

提前感谢您


1
这个回答解决了你的问题吗?如何使用Spring RestTemplate消费Page<Entity>响应 - Aekansh Kansal
8个回答

40

当从Spring Boot 1.x迁移到2.0时, 更改了读取Rest API响应的代码为

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;

import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.List;

public class RestPageImpl<T> extends PageImpl<T>{

    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestPageImpl(@JsonProperty("content") List<T> content,
                        @JsonProperty("number") int number,
                        @JsonProperty("size") int size,
                        @JsonProperty("totalElements") Long totalElements,
                        @JsonProperty("pageable") JsonNode pageable,
                        @JsonProperty("last") boolean last,
                        @JsonProperty("totalPages") int totalPages,
                        @JsonProperty("sort") JsonNode sort,
                        @JsonProperty("first") boolean first,
                        @JsonProperty("numberOfElements") int numberOfElements) {

        super(content, PageRequest.of(number, size), totalElements);
    }

    public RestPageImpl(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestPageImpl(List<T> content) {
        super(content);
    }

    public RestPageImpl() {
        super(new ArrayList<>());
    }
}

4
除了排序字段之外,这适用于所有东西,有人发现如何反序列化和存储排序字段吗? - chrisinmtown

23

更改了读取 Rest API 响应的代码如下:

ParameterizedTypeReference<RestResponsePage<MyObject>> responseType = new ParameterizedTypeReference<RestResponsePage<MyObject>>() { };

ResponseEntity<RestResponsePage<MyObject>> result = restTemplate.exchange(url, HttpMethod.GET, null/*httpEntity*/, responseType);

List<MyObject> searchResult = result.getBody().getContent();

这是我为RestResponsePage创建的类:

package com.basf.gb.cube.seq.vaadinui.util;

import java.util.ArrayList;
import java.util.List;

import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

public class RestResponsePage<T> extends PageImpl<T>{

  private static final long serialVersionUID = 3248189030448292002L;

  public RestResponsePage(List<T> content, Pageable pageable, long total) {
    super(content, pageable, total);
    // TODO Auto-generated constructor stub
  }

  public RestResponsePage(List<T> content) {
    super(content);
    // TODO Auto-generated constructor stub
  }

  /* PageImpl does not have an empty constructor and this was causing an issue for RestTemplate to cast the Rest API response
   * back to Page.
   */
  public RestResponsePage() {
    super(new ArrayList<T>());
  }

} 

为什么要传入空的httpEntity?null /httpEntity/ - Robbo_UK
1
这并没有返回正确的页面数或总结果数。 - ACOMIT001
1
@turgos - 我们如何使用Spring HATEOAS使其工作?使用PagedResourcesAssembler? - PAA

10

在上面的基础上,进一步扩展,但不需要实现每个属性。

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.List;

public class RestPageImpl<T> extends PageImpl<T>{

    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestPageImpl(@JsonProperty("content") List<T> content,
                        @JsonProperty("number") int page,
                        @JsonProperty("size") int size,
                        @JsonProperty("totalElements") long total) {
        super(content, new PageRequest(page, size), total);
    }

    public RestPageImpl(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestPageImpl(List<T> content) {
        super(content);
    }

    public RestPageImpl() {
        super(new ArrayList());
    }
}

1
我尝试过这个,但是如果Spring返回一个JSON结构,其中包含“first”或其他未在构造函数中命名的字段,则会抛出com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException。 - chrisinmtown
@chrisinmtown 我在类上添加了 @JsonIgnoreProperties(ignoreUnknown = true),并且我将类命名为 RestPage,没有使用那个老旧的 Impl 后缀。 - kiedysktos
2
对于Spring 2.3.3,我遇到了编译错误:new PageRequest(page, size)访问受保护。您可以使用PageRequest.of(page, size) - Praytic

7

我不得不进行一些小的更改,以便忽略最近引入的未知属性empty。

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public class RestResponsePage<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestResponsePage(@JsonProperty("content") List<T> content,
                            @JsonProperty("number") int number,
                            @JsonProperty("size") int size,
                            @JsonProperty("totalElements") Long totalElements,
                            @JsonProperty("pageable") JsonNode pageable,
                            @JsonProperty("last") boolean last,
                            @JsonProperty("totalPages") int totalPages,
                            @JsonProperty("sort") JsonNode sort,
                            @JsonProperty("first") boolean first,
                            @JsonProperty("numberOfElements") int numberOfElements) {

        super(content, PageRequest.of(number, size), totalElements);
    }

    public RestResponsePage(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestResponsePage(List<T> content) {
        super(content);
    }

    public RestResponsePage() {
        super(new ArrayList<>());
    }
}

5

不需要实现 Page。您只需在 ParameterizedTypeReference 中使用 PagedResources<T> 作为类型即可。

因此,如果您的服务返回类似于以下响应(为了简洁而删除了对象):

{
    "_embedded": {
        "events": [
            {...},
            {...},
            {...},
            {...},
            {...}
        ]
    },
    "_links": {
        "first": {...},
         "self": {...},
         "next": {...},
         "last": {...}
    },
    "page": {
        "size": 5,
        "totalElements": 30,
        "totalPages": 6,
        "number": 0
    }
}

如果您关心的对象是Event类型,那么您应该像这样执行请求:

ResponseEntity<PagedResources<Event>> eventsResponse = restTemplate.exchange(uriBuilder.build(true).toUri(),
                HttpMethod.GET, null, new ParameterizedTypeReference<PagedResources<Event>>() {});

如果您掌握以下资源:

PagedResources<Event> eventsResources = eventsResponse.getBody();

你将能够访问页面元数据(在"page"部分获取的内容)、链接("_links"部分)和内容。
Collection<Event> eventsCollection = eventsResources.getContent();

1
当遵循这个内容时,可以访问该内容,但我的元数据始终为空。虽然无法迭代页面,因为我无法获取总元素数。有其他人使这个例子工作了吗? 我期望此请求的内容是“List<String>” 这是我的代码: ResponseEntity<PagedResources<String>> response = restTemplate.exchange(targetUrl, HttpMethod.GET, requestEntity, new ParameterizedTypeReference<PagedResources<String>>() {}); - froehli
@Vladimir - 你能帮我指导一下吗?链接在这里:https://stackoverflow.com/questions/59104894/pagedresourcesresource-and-resttemplate-does-not-works? - PAA
@Vladimir - 这些解决方案是否适用于 PagedResourcesAssembler? - PAA
1
PageModel(PagedResources)适用于Page吗? - Jin Kwon

5

Kotlin中的解决方案

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.JsonNode
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable

import java.util.ArrayList

class RestResponsePage<T> : PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    constructor(@JsonProperty("content") content: List<T>,
                @JsonProperty("number") number: Int,
                @JsonProperty("size") size: Int,
                @JsonProperty("totalElements") totalElements: Long?,
                @JsonProperty("pageable") pageable: JsonNode,
                @JsonProperty("last") last: Boolean,
                @JsonProperty("totalPages") totalPages: Int,
                @JsonProperty("sort") sort: JsonNode,
                @JsonProperty("first") first: Boolean,
                @JsonProperty("numberOfElements") numberOfElements: Int) : super(content, PageRequest.of(number, size), totalElements!!) {
    }

    constructor(content: List<T>, pageable: Pageable, total: Long) : super(content, pageable, total) {}

    constructor(content: List<T>) : super(content) {}

    constructor() : super(ArrayList<T>()) {}
}

和请求

var response: ResponseEntity<*> = restTemplate.getForEntity<RestResponsePage<SomeObject>>(url)

Java解决方案

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.List;

public class RestResponsePage<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestResponsePage(@JsonProperty("content") List<T> content,
                        @JsonProperty("number") int number,
                        @JsonProperty("size") int size,
                        @JsonProperty("totalElements") Long totalElements,
                        @JsonProperty("pageable") JsonNode pageable,
                        @JsonProperty("last") boolean last,
                        @JsonProperty("totalPages") int totalPages,
                        @JsonProperty("sort") JsonNode sort,
                        @JsonProperty("first") boolean first,
                        @JsonProperty("numberOfElements") int numberOfElements) {

        super(content, PageRequest.of(number, size), totalElements);
    }

    public RestResponsePage(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestResponsePage(List<T> content) {
        super(content);
    }

    public RestResponsePage() {
        super(new ArrayList<>());
    }
}

RestResponsePage 对于 Kotlin 在另一个用例中对我很有帮助,谢谢。 - Nalmelune

0

发布的解决方案对我无效,因为元素总数没有正确设置。我用委托模式实现了页面,这对我有用。以下是可运行的代码:

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RestPage<T> implements Page<T> {
    private PageImpl<T> pageDelegate = new PageImpl<>(new ArrayList<>(0));

    public List<T> getContent() {
        return pageDelegate.getContent();
    }

    public int getNumber() {
        return pageDelegate.getNumber();
    }

    public int getNumberOfElements() {
        return pageDelegate.getNumberOfElements();
    }

    public int getSize() {
        return pageDelegate.getSize();
    }

    public Sort getSort() {
        return pageDelegate.getSort();
    }

    public long getTotalElements() {
        return pageDelegate.getTotalElements();
    }

    public int getTotalPages() {
        return pageDelegate.getTotalPages();
    }

    public boolean hasContent() {
        return pageDelegate.hasContent();
    }

    public boolean hasNext() {
        return pageDelegate.hasNext();
    }

    public boolean hasPrevious() {
        return pageDelegate.hasPrevious();
    }

    public boolean isFirst() {
        return pageDelegate.isFirst();
    }

    public boolean isLast() {
        return pageDelegate.isLast();
    }

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

    public <S> Page<S> map(Converter<? super T, ? extends S> converter) {
        return pageDelegate.map(converter);
    }

    public Pageable nextPageable() {
        return pageDelegate.nextPageable();
    }

    public Pageable previousPageable() {
        return pageDelegate.previousPageable();
    }

    public void setContent(List<T> content) {
        pageDelegate = new PageImpl<>(content, null, getTotalElements());
    }


    public void setTotalElements(int totalElements) {
        pageDelegate = new PageImpl<>(getContent(), null, totalElements);
    }

    public String toString() {
        return pageDelegate.toString();
    }
}

这只处理了一个参数构造函数的情况。如果你有一个三个参数的构造函数,那么这就没有用了。 - cst1992
1
除了totalElements和content字段之外,其他字段不会使用以上代码进行填充。 - MasterCode

-1

可以通过配置属性/yml激活,只需使用:feign.autoconfiguration.jackson.enabled=true - rodrix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接