RestTemplate映射页面“页面大小不能小于一个" [英] RestTemplate mapping Page 'Page size must not be less than one'

查看:93
本文介绍了RestTemplate映射页面“页面大小不能小于一个"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个REST API端点,它将返回Page<User>.我想为此端点提供一些测试,我发现了这个问题,我可以使用另一个答案具有另一个实现,即

I have a REST API endpoint that will return a Page<User>. I want to provide some tests for this endpoint, and I found this question, of which I could use this answer (The accepted answer is outdated). When I tried to implement this, I noticed that another answer had another implementation, which was supposedly for spring 2.0.

但是,使用此代码时(下面的代码);我从映射器中得到一个例外.我注意到在Wireshark中,正确返回了Page并将其填充.当我使用邮递员之类的工具手动发出请求时,我也正确地获得了Page.

However, when using this (code below); I get an exception from the mapper. I noticed that in Wireshark the Page is returned correctly, and filled. When I use a tool like Postman to manually make the request, I also correctly get the Page.

控制器:

@GetMapping("/")
Page<User> findAll(@RequestParam("page") int page, @RequestParam("size") int size);

RestResponsePage(在此处找到):

RestResponsePage (as found here):

package org.company.product.userservice.helpers;

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

测试:

private OAuth2RestTemplate template;

/** template init code, retrieving token **/

UriComponentsBuilder uri = UriComponentsBuilder.fromHttpUrl(ZUUL_URI + "/api/users")
        .queryParam("page", 0)
        .queryParam("size", 10);
ResponseEntity<RestResponsePage<User>> user = template.exchange(uri.build().encode().toUri(), HttpMethod.GET, null, new ParameterizedTypeReference<RestResponsePage<User>>(){});

运行此命令会得到以下堆栈跟踪:

Running this gives the following stacktrace:

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.company.project.userservice.helpers.RestResponsePage]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of 'org.company.project.userservice.helpers.RestResponsePage;', problem: Page size must not be less than one!
 at [Source: (PushbackInputStream); line: 63, column: 1]

    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:242)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:227)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:102)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:994)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:977)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:736)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:709)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:627)
    at org.company.project.userservice.api.OAuthMvcTest.testAddingAddressToUser(OAuthMvcTest.java:134)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.base/java.util.ArrayList.forEach(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.base/java.util.ArrayList.forEach(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of 'org.company.project.userservice.helpers.RestResponsePage', problem: Page size must not be less than one!
 at [Source: (PushbackInputStream); line: 63, column: 1]
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1608)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.wrapAsJsonMappingException(StdValueInstantiator.java:484)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.rewrapCtorProblem(StdValueInstantiator.java:503)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromObjectWith(StdValueInstantiator.java:285)
    at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromObjectWith(ValueInstantiator.java:229)
    at com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:195)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:488)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1287)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:239)
    ... 48 more
Caused by: java.lang.IllegalArgumentException: Page size must not be less than one!
    at org.springframework.data.domain.AbstractPageRequest.<init>(AbstractPageRequest.java:50)
    at org.springframework.data.domain.PageRequest.<init>(PageRequest.java:71)
    at org.springframework.data.domain.PageRequest.of(PageRequest.java:96)
    at org.springframework.data.domain.PageRequest.of(PageRequest.java:84)
    at org.company.project.userservice.helpers.RestResponsePage.<init>(RestResponsePage.java:28)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.base/java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.fasterxml.jackson.databind.introspect.AnnotatedConstructor.call(AnnotatedConstructor.java:124)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromObjectWith(StdValueInstantiator.java:283)
    ... 57 more

我注意到杰克逊追溯使用的对象时,使用了空值和零值.但是,在BeanDeserializer._deserializeUsingPropertyBased()函数中,我发现实际上存在正确的User对象:

I noticed that when tracing back the used objects by Jackson, that there are null values and zero values used. However, in the BeanDeserializer._deserializeUsingPropertyBased() the function I found that the correct User objects are in fact present:

我应该在帮助器类中进行哪些更改以解决此问题,并将响应正确映射到Page<User>?

What I should change in the helper class to fix this behaviour and correctly map the response to a Page<User>?

推荐答案

解决方案: 下面是一个示例代码,在我的spring boot项目中可以正常工作.

Solution: The below is a sample code that works fine in my spring boot project.

步骤(1): 创建一个扩展PageImpl<>的自定义类.因为PageImpl<>没有空的构造函数,因此不能在反序列化中使用.另外请注意,杰克逊将使用以下注释.

Step(1): Create a custom class that extends the PageImpl<>. because the PageImpl<> does not have empty constructor and therefore can not be used in deserialization. Also note that the below annotations will be used by jackson.

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 JacksonPageImpl<T> extends PageImpl<T> {

    private static final long serialVersionUID = 1230183330548294567L;

    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public JacksonPageImpl(@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 JacksonPageImpl(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

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

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

}

步骤(2): 现在调用如下所示的方法.
注意:您可以在exchange方法的最后一个参数中直接使用WrapperRestResponse.class.但是在这种情况下,JacksonPageImpl.getContent()会为您提供List<LinkedHashMap>而不是List<T>,因为默认情况下,杰克逊将JSON对象反序列化为LinkedHashMap.因此在下面,我使用ParameterizedTypeReference在结果中添加了List<T>.

Step(2): Now call a method like as the below.
Note: You can directly use WrapperRestResponse.class in the last argument of the exchange method. but in this case, the JacksonPageImpl.getContent() gives you a List<LinkedHashMap> instead of List<T>, becuase Jackson by default deserializes a JSON object into a LinkedHashMap. So in the below I used the ParameterizedTypeReference to have List<T> in the result.

private String getPages(int pageNumber, int pageSize, ...){
    String url = "https://example.com/your_endpoint?page=" + pageNumber + "&size="+pageSize;
    //for example here: your_endpoint = endpoint1/endpoint2

    HttpHeaders headers = new HttpHeaders();
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    //if you have authorization (basic,oauth,...) in server side:
    //headers.add("Authorization", ...);

    HttpEntity<String> request = new HttpEntity<>(headers);

    RestTemplate restTemplate = new RestTemplate();   
    ParameterizedTypeReference<WrapperRestResponse<T>> typeRef = new ParameterizedTypeReference<WrapperRestResponse<T>>(){};

    ResponseEntity<WrapperRestResponse> rsp = restTemplate.exchange(url, HttpMethod.GET, request, typeRef);
    if(rsp.getStatusCodeValue() == 200){
        WrapperRestResponse<T> pojoAnswer = rsp.getBody();
        JacksonPageImpl<T> page = pojoAnswer.getPage();
        List<T> contents = page.getContent();
        //continue your logic...
    }
    //...
}

步骤(3):我使用了WrapperRestResponse如下所示,以便您可以简单地传递Page<>之外的其他属性:

step(3): I used a WrapperRestResponse as the below, so that you can pass other properties simply in addition to Page<>:

public class WrapperRestResponse<T> {

    private JacksonPageImpl<T> page;
    //you can have other fields too

    public WrapperRestResponse() {
    }
    public WrapperRestResponse(JacksonPageImpl<T> page, ...) {
         this.page = page;
         //other fields...
    }

    public JacksonPageImpl<T> getPage() {
        return page;
    }

    //other fileds getter & setters

}

步骤(4):这是我的端点:

@RestController
@RequestMapping(value = "/endpoint1", produces = "application/json")
public class ResourceController {

    //...

    @GetMapping("/endpoint2")
    public ResponseEntity getPages(Pageable pageable, ...){
        Page<T> pages = ...;//get it by your logic
        //...
        return ResponseEntity.ok(new WrapperRestResponse(pages, ...));
    }
}

希望对您有帮助.

这篇关于RestTemplate映射页面“页面大小不能小于一个"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆