尝试进行单元测试以从Spring Boot接收页面。如果与javascript一起使用,则可以很容易地反序列化页面,但是对于java,页面将失败。已经为spring添加了默认的构造函数(这是另一个stackoverflow帖子中可接受的答案),但是在这里不起作用。
单元测试
@Test
public void test_read_pagination_happy(@Autowired ObservationSet set) {
repository.save(set);
final HttpEntity<String> authHeaders = authentication.convert("", authSuccess);
final ParameterizedTypeReference<RestResponsePage<ObservationSet>> responseType = new ParameterizedTypeReference<RestResponsePage<ObservationSet>>() {
};
// final ResponseEntity<String> result = restTemplate.exchange(base + "/api/v1/observationset", HttpMethod.GET, authHeaders, String.class);
final ResponseEntity<RestResponsePage<ObservationSet>> result = restTemplate.exchange(base + "/api/v1/observationset", HttpMethod.GET, authHeaders,
responseType);
System.out.println(result.getBody());
assertSame(HttpStatus.OK, result.getStatusCode(), "incorrect status code");
}
RestRespongePage类
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);
}
public RestResponsePage(List<T> content) {
super(content);
}
public RestResponsePage() {
super(new ArrayList<T>());
}
}
```
反序列化引发此错误:
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Pageable]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (PushbackInputStream); line: 1, column: 294] (through reference chain: org.openpcm.controller.RestResponsePage["pageable"])
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:240)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:225)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:100)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:959)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:942)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:689)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:644)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:593)
at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:843)
at org.openpcm.controller.ObservationSetControllerIntTest.test_read_pagination_happy(ObservationSetControllerIntTest.java:85)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:436)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:112)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:430)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:430)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (PushbackInputStream); line: 1, column: 294] (through reference chain: org.openpcm.controller.RestResponsePage["pageable"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
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:237)
... 65 more
答案 0 :(得分:2)
在此处的其他帖子中找到了答案。这不是应该接受的答案
Spring RestTemplate with paginated API
@rvheddeg的回答是正确的。您只需要放入@JsonCreator并为构造函数提供所有属性,这是我的RestResponsePage类,可以解决此问题。
class RestResponsePage<T> extends PageImpl<T> {
private static final long serialVersionUID = 3248189030448292002L;
@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<T>());
}
}
答案 1 :(得分:1)
实际上,spring mvc是通过jackson序列化对象的,因此注入pageImp序列化程序应该可以解决问题。
步骤1 :PageSerializer类:
public class PageSerializer extends StdSerializer<PageImpl> {
public PageSerializer() {
super(PageImpl.class);
}
@Override
public void serialize(PageImpl value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("number", value.getNumber());
gen.writeNumberField("numberOfElements", value.getNumberOfElements());
gen.writeNumberField("totalElements", value.getTotalElements());
gen.writeNumberField("totalPages", value.getTotalPages());
gen.writeNumberField("size", value.getSize());
gen.writeFieldName("content");
provider.defaultSerializeValue(value.getContent(), gen);
gen.writeEndObject();
}
}
第2步:注入杰克逊·穆德:
@Bean
public Module jacksonPageWithJsonViewModule() {
SimpleModule module = new SimpleModule("jackson-page-with-jsonview",
unknownVersion());
module.addSerializer(PageImpl.class, new PageSerializer());
return module;
}
好的,结束。
答案 2 :(得分:1)
这有效(org.springframework.cloud:spring-cloud-openfeign-core:2.2.5.RELEASE):
@Configuration
public class FeignConfigurationFactory {
@Bean
public Module pageJacksonModule() {
return new PageJacksonModule();
}
@Bean
public Module sortJacksonModule() {
return new SortJacksonModule();
}
}
答案 3 :(得分:0)
必须进行一些小的更改以忽略empty的未知属性:
package ...helper;
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<>());
}
}
答案 4 :(得分:0)
有一种简便的方法-为Page界面创建和注册自定义反序列化器。 因此用法很简单:
//Catalog is the paged entity
Page<Catalog> page = objectMapper.readValue(content, new TypeReference<Page<Catalog>>() {});
ObjectMapper配置:
ObjectMapper objectMapper= new ObjectMapper();
objectMapper.registerModule(new PageModule());
PageModule:
public class PageModule extends SimpleModule {
private static final long serialVersionUID = 1L;
public PageModule() {
addDeserializer(Page.class, new PageDeserializer());
}
}
PageDeserializer:
public class PageDeserializer extends JsonDeserializer<Page<?>> implements ContextualDeserializer {
private static final String CONTENT = "content";
private static final String NUMBER = "number";
private static final String SIZE = "size";
private static final String TOTAL_ELEMENTS = "totalElements";
private JavaType valueType;
@Override
public Page<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
final CollectionType valuesListType = ctxt.getTypeFactory().constructCollectionType(List.class, valueType);
List<?> list = new ArrayList<>();
int pageNumber = 0;
int pageSize = 0;
long total = 0;
if (p.isExpectedStartObjectToken()) {
p.nextToken();
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
switch (propName) {
case CONTENT:
list = ctxt.readValue(p, valuesListType);
break;
case NUMBER:
pageNumber = ctxt.readValue(p, Integer.class);
break;
case SIZE:
pageSize = ctxt.readValue(p, Integer.class);
break;
case TOTAL_ELEMENTS:
total = ctxt.readValue(p, Long.class);
break;
default:
p.skipChildren();
break;
}
} while (((propName = p.nextFieldName())) != null);
} else {
ctxt.handleUnexpectedToken(handledType(), p);
}
} else {
ctxt.handleUnexpectedToken(handledType(), p);
}
//Note that Sort field of Page is ignored here.
//Feel free to add more switch cases above to deserialize it as well.
return new PageImpl<>(list, PageRequest.of(pageNumber, pageSize), total);
}
/**
* This is the main point here.
* The PageDeserializer is created for each specific deserialization with concrete generic parameter type of Page.
*/
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
//This is the Page actually
final JavaType wrapperType = ctxt.getContextualType();
final PageDeserializer deserializer = new PageDeserializer();
//This is the parameter of Page
deserializer.valueType = wrapperType.containedType(0);
return deserializer;
}
}
答案 5 :(得分:0)
你可以这样做
final PageImplDeserializer<YOUR_CLASS> response = objectMapper.readValue(jsonResponse, new TypeReference<>() {});
public class PageImplDeserializer<T> {
private List<T> content;
private int number;
private int size;
private Long totalElements;
private JsonNode pageable;
private boolean last;
private int totalPages;
private JsonNode sort;
private boolean first;
private int numberOfElements;
... HERE GETTERS AND SETTERS...
}
答案 6 :(得分:0)
如果您使用响应并对其进行序列化,则这些解决方案在测试领域中不起作用。我必须自己序列化 List 中的内容 :)
在这种情况下,我使用的是 RestAssured 响应。
protected static <T> List<T> getResponsePageDTO(Response response, Class<T> clazz) throws IOException {
Map<String, Object> objectMap = objectMapper.readValue(response.getBody().print(), Map.class);
List<Object> content = (List<Object>) objectMap.get("content");
List<T> result = new ArrayList<>();
for (Object o : content) {
result.add(objectMapper.convertValue(o, clazz));
}
return result;
}