Spring 带分页的 RestTemplate API
Spring RestTemplate with paginated API
我们的 REST APIs 在页面中返回结果。这是一个控制器的例子
@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
提前致谢
将读取 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
创建的 class
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>());
}
}
发布的解决方案对我不起作用,因为总元素设置不正确。我用委托模式实现了页面,这对我很有用。这是工作代码:
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();
}
}
在上面进行扩展,但不需要实现每个 属性。
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());
}
}
没有必要实施 Page
。您只需在 ParameterizedTypeReference
.
中使用 PagedResources<T>
作为类型
因此,如果您的服务 returns 响应类似于(为简洁起见删除了对象):
{
"_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();
从 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<>());
}
}
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<>());
}
}
我不得不做一个小改动,这样它就可以忽略最近引入的未知 属性 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<>());
}
}
如果你使用 FeignClient,有一个简单的解决方案,创建一个配置 class。
作者:
https://github.com/spring-cloud/spring-cloud-openfeign/issues/205
配置class:
public class FeignDecodeConfiguration {
@Bean
public Module pageJacksonModule () {
return new PageJacksonModule ();
}
}
我们的 REST APIs 在页面中返回结果。这是一个控制器的例子
@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
提前致谢
将读取 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
创建的 classpackage 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>());
}
}
发布的解决方案对我不起作用,因为总元素设置不正确。我用委托模式实现了页面,这对我很有用。这是工作代码:
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();
}
}
在上面进行扩展,但不需要实现每个 属性。
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());
}
}
没有必要实施 Page
。您只需在 ParameterizedTypeReference
.
PagedResources<T>
作为类型
因此,如果您的服务 returns 响应类似于(为简洁起见删除了对象):
{
"_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();
从 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<>());
}
}
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<>());
}
}
我不得不做一个小改动,这样它就可以忽略最近引入的未知 属性 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<>());
}
}
如果你使用 FeignClient,有一个简单的解决方案,创建一个配置 class。
作者: https://github.com/spring-cloud/spring-cloud-openfeign/issues/205
配置class:
public class FeignDecodeConfiguration {
@Bean
public Module pageJacksonModule () {
return new PageJacksonModule ();
}
}