Попытка юнит-теста получить страницу из весенней загрузки. При использовании с 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>());
}
}
`` ''
Код доступен на github -> https://github.com/OpenPCM/openpcm-server/blob/integration-test/src/test/java/org/openpcm/controller/ObservationSetControllerIntTest.java
Десериализация вызывает эту ошибку:
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
Обходной путь заключается в том, чтобы не раскрывать Page в качестве типа ответа от конечной точки контроллера и вместо этого возвращать List <YourEntity>. В Page есть метод getContent(), который можно вызвать для преобразования страницы в список <YourEntity>. Как я уже сказал, не исправление, а обходной путь.
@alexrolea я действительно хочу вернуть страницу, но не только список, поэтому, если вы находитесь в веб-клиенте, вы можете переходить через страницы
@Prashant удалил картинку и добавил текст
@GSUgambit, если клиент отслеживает что-то вроде pageIndex и pageSize на своей стороне и отправляет два параметра с запросом, вам действительно не нужно открывать Page клиенту. Но, как я уже сказал, это скорее обходной путь, чем исправление :)
@alexrolea, если вы не отправите страницу обратно, клиент не знает количество страниц и т. д.
@GSUgambit, если у вас клиент без графического интерфейса, вы правы. Большинство клиентов с графическим пользовательским интерфейсом отслеживают эту информацию (pageIndex и pageSize) по состоянию таблицы, в которой вы бы отображали ObservationSet. Обычно есть раскрывающийся список с items per page с предопределенными размерами (соответствует pageSize) и кнопками с некоторым состоянием (page 0, page1, next, previous, first, last), которые отображаются на pageIndex. Состояния кнопки индекса вычисляются графическим интерфейсом пользователя, сначала отправляя запрос на счетчик, чтобы определить, сколько страниц там (чтобы вы могли перейти к последней странице, не повторяя все).
да, но если вы скроете объекты страницы "numberOfPages" и т. д. из ответа, пользовательский интерфейс не будет знать, сколько существует возможных страниц. Вы можете жестко закодировать, сколько элементов вы хотите отображать на странице, но вы не можете знать общие номера страниц, которые не позволят вам отображать page1 ... page127 и т. д., Вы в основном будете знать только номера страниц, которые существуют после того, как вы сделаете звонки и вернуть другое количество элементов. Я нашел ответ о том, как заставить эту работу работать, хотя сейчас
Возможный дубликат Spring RestTemplate с разбитым на страницы API
Отвечает ли это на ваш вопрос? Как использовать ответ Page <Entity> с помощью Spring RestTemplate




Нашел ответ в другом посте здесь. Это не был принятый ответ, который должен быть
Spring RestTemplate с разбитым на страницы 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>());
}
}
Для Spring 2.3.3 у меня ошибка компиляции: new PageRequest(page, size) имеет защищенный доступ. Вы можете использовать PageRequest.of(page, size).
Пришлось внести небольшое изменение, чтобы игнорировать неизвестное свойство пустого:
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<>());
}
}
Есть более простой способ сделать это - создать и зарегистрировать собственный десериализатор для интерфейса страницы. Таким образом, использование будет простым:
//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;
}
}
Это так же хорошо, как принятый ответ, некоторые другие альтернативы могут заставить Blockhound сообщать о блокирующем вызове при десериализации страницы. Я использую Spring WebFlux.
Фактически, Spring mvc сериализует объект по Джексону, поэтому внедрение сериализатора 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: ввести Джексон Moudle:
@Bean
public Module jacksonPageWithJsonViewModule() {
SimpleModule module = new SimpleModule("jackson-page-with-jsonview",
unknownVersion());
module.addSerializer(PageImpl.class, new PageSerializer());
return module;
}
хорошо, конец.
Это сработало (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();
}
}
Я получил java.lang.ClassNotFoundException: feign.codec.EncodeException с org.springframework.cloud:spring-cloud-openfeign-core при запуске как jar. Вместо этого нужно использовать org.springframework.cloud:spring-cloud-starter-openfeign.
Вы можете сделать что-то вроде этого
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...
}
Эти решения не работают во вселенной тестов, если вы используете ответ и сериализуете его. Мне пришлось самому сериализовать контент в 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;
}
Предоставьте текст исключения. (См .: idownvotedbecau.se/imageofanexception)