web-dev-qa-db-fra.com

Spring RestTemplate avec une API paginée

Nos API REST renvoient des résultats dans Pages. Voici un exemple de contrôleur

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

Existe-t-il un moyen simple de consommer cette API avec RestTemplate?

si nous faisons

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();

il jette une exception 

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

Merci d'avance

23
turgos

Modification du code lisant la réponse de l'API Rest en tant que;

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();

Et voici la classe que j'ai créée pour 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>());
  }

} 
11
turgos

Lors de la migration de Spring Boot 1.x vers la version 2.0, A modifié le code lisant la réponse de l'API Rest

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<>());
    }
}
14
장재훈

En développant ci-dessus, mais sans la nécessité de mettre en œuvre chaque propriété.

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());
    }
}
8
Stephen

Il n’est pas nécessaire de mettre en œuvre une Page. Vous devez simplement utiliser un PagedResources<T> comme type dans votre ParameterizedTypeReference.

Ainsi, si votre service renvoie une réponse similaire à (les objets sont supprimés pour des raisons de brièveté):

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

Et les objets que vous entretenez sont de type Event, alors vous devez exécuter la requête comme suit:

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

Si vous obtenez des ressources telles que:

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

vous pourrez accéder aux métadonnées de la page (ce que vous obtenez dans la section "page"), aux liens (section "_links") et au contenu:

Collection<Event> eventsCollection = eventsResources.getContent();
2
Vladimir Mitev

J'ai dû faire un petit changement pour ignorer la propriété inconnue de empty qui semble avoir été introduite récemment.

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<>());
    }
}
0
Loren

La solution affichée ne fonctionnait pas pour moi car le total des éléments n'était pas correctement défini. J'ai implémenté la page avec un modèle de délégué, ce qui a fonctionné pour moi. Voici le code de travail:

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();
    }
}
0
Simon Flandergan

Solution à 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>()) {}
}

et demande

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

Solution en 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<>());
    }
}
0
Danijel Sudar