복붙노트

[SPRING] 페이지가 매겨진 API가있는 Spring RestTemplate

SPRING

페이지가 매겨진 API가있는 Spring RestTemplate

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

예외를 throw합니다.

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. ==============================

    1.스프링 부트 1.x에서 2.0으로 마이그레이션 할 때, 나머지 API 응답을 읽는 코드가 다음과 같이 변경되었습니다.

    스프링 부트 1.x에서 2.0으로 마이그레이션 할 때, 나머지 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<>());
        }
    }
    
  2. ==============================

    2.Rest API 응답을 읽는 코드를 다음과 같이 변경했습니다.

    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>());
      }
    
    } 
    
  3. ==============================

    3.위에서 확장하면 모든 속성을 구현할 필요가 없습니다.

    위에서 확장하면 모든 속성을 구현할 필요가 없습니다.

    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());
        }
    }
    
  4. ==============================

    4.페이지를 구현할 필요가 없습니다. ParameterizedTypeReference의 형식으로 PagedResources 를 사용해야합니다.

    페이지를 구현할 필요가 없습니다. ParameterizedTypeReference의 형식으로 PagedResources 를 사용해야합니다.

    따라서 서비스가 다음과 비슷한 응답을 반환하는 경우 (간결성을 위해 객체가 제거됨) :

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

    그리고 당신이 관심을 갖는 객체는 다음과 같이 요청을 실행해야합니다.

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

    다음과 같은 리소스를 확보 한 경우 :

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

    페이지 메타 데이터 ( '페이지'섹션에서 얻은 정보), 링크 ( '_ 링크'섹션) 및 콘텐츠에 액세스 할 수 있습니다.

    Collection<Event> eventsCollection = eventsResources.getContent();
    
  5. ==============================

    5.전체 요소가 올바르게 설정되지 않았기 때문에 게시 된 솔루션이 저에게 효과적이지 않았습니다. 나는 위임 패턴을 사용하여 페이지를 구현했다. 다음은 작동 코드입니다.

    전체 요소가 올바르게 설정되지 않았기 때문에 게시 된 솔루션이 저에게 효과적이지 않았습니다. 나는 위임 패턴을 사용하여 페이지를 구현했다. 다음은 작동 코드입니다.

    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();
        }
    }
    
  6. from https://stackoverflow.com/questions/34647303/spring-resttemplate-with-paginated-api by cc-by-sa and MIT license