Перейти к основному содержимому

Получить список объектов JSON с помощью WebClient

· 5 мин. чтения

1. Обзор

Наши службы часто взаимодействуют с другими службами REST для получения информации.

Начиная с Spring 5, мы можем использовать WebClient для выполнения этих запросов реактивным, неблокирующим способом. WebClient является частью нового WebFlux Framework, построенного поверх Project Reactor . Он имеет свободный, реактивный API и использует протокол HTTP в своей базовой реализации.

Когда мы делаем веб-запрос, данные часто возвращаются в формате JSON. WebClient может преобразовать это для нас.

В этой статье мы узнаем, как преобразовать массив JSON в массив объектов Java , массив POJO и список POJO с помощью WebClient . [](/lessons/b/-java-pojo-class) ``

2. Зависимости

Чтобы использовать WebClient, нам нужно добавить пару зависимостей в наш pom.xml:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectreactor</groupId>
<artifactId>reactor-spring</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>

3. JSON, POJO и сервис

Начнем с конечной точки http://localhost:8080/readers , которая возвращает список читателей с их любимыми книгами в виде массива JSON:

[{
"id": 1,
"name": "reader1",
"favouriteBook": {
"author": "Milan Kundera",
"title": "The Unbearable Lightness of Being"
}
}, {
"id": 2,
"name": "reader2"
"favouriteBook": {
"author": "Douglas Adams",
"title": "The Hitchhiker's Guide to the Galaxy"
}
}]

Нам потребуются соответствующие классы Reader и Book для обработки данных:

public class Reader {
private int id;
private String name;
private Book favouriteBook;

// getters and setters..
}
public class Book {
private final String author;
private final String title;

// getters and setters..
}

Для реализации нашего интерфейса мы пишем ReaderConsumerServiceImpl с WebClient в качестве зависимости:

public class ReaderConsumerServiceImpl implements ReaderConsumerService {

private final WebClient webClient;

public ReaderConsumerServiceImpl(WebClient webclient) {
this.webclient = webclient;
}

// ...
}

4. Сопоставление списка объектов JSON

Когда мы получаем массив JSON из запроса REST, существует несколько способов преобразовать его в коллекцию Java. Давайте рассмотрим различные варианты и посмотрим, насколько просто обрабатывать возвращаемые данные. Мы рассмотрим извлечение любимых книг читателей.

4.1. Моно против потока

Project Reactor представил две реализации Publisher: Mono и Flux .

Flux<T> полезен, когда нам нужно обработать от нуля до многих или потенциально бесконечных результатов. В качестве примера можно привести ленту Twitter.

Когда мы знаем, что результаты возвращаются сразу — как в нашем случае использования — мы можем использовать Mono<T> .

4.2. Веб-клиент с массивом объектов

Во-первых, давайте сделаем вызов GET с помощью WebClient.get и используем Mono типа Object[] для получения ответа:

Mono<Object[]> response = webClient.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Object[].class).log();

Далее давайте извлечем тело в наш массив Object :

Object[] objects = response.block();

Фактический объект здесь представляет собой произвольную структуру, содержащую наши данные. Преобразуем его в массив объектов Reader .

Для этого нам понадобится ObjectMapper :

ObjectMapper mapper = new ObjectMapper();

Здесь мы объявили его встроенным, хотя обычно это делается как закрытый статический конечный член класса.

Наконец, мы готовы извлечь любимые книги читателей и собрать их в список:

return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, Reader.class))
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());

Когда мы просим десериализатор Джексона создать Object в качестве целевого типа, он фактически десериализует JSON в серию объектов LinkedHashMap . Постобработка с convertValue неэффективна. Мы можем избежать этого, если предоставим желаемый тип Джексону во время десериализации.

4.3. Веб-клиент с массивом чтения

Мы можем предоставить Reader[] вместо Object[] нашему WebClient :

Mono<Reader[]> response = webClient.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Reader[].class).log();
Reader[] readers = response.block();
return Arrays.stream(readers)
.map(Reader:getFavouriteBook)
.collect(Collectors.toList());

Здесь мы можем заметить, что нам больше не нужен ObjectMapper.convertValue . Однако нам по-прежнему необходимо выполнять дополнительные преобразования для использования Java Stream API и для того, чтобы наш код работал со списком .

4.4. Веб-клиент со списком читателей ``

Если мы хотим, чтобы Джексон создавал List of Reader вместо массива, нам нужно описать List , который мы хотим создать. Для этого мы предоставляем методу ParameterizedTypeReference , созданный анонимным внутренним классом :

Mono<List<Reader>> response = webClient.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Reader>>() {});
List<Reader> readers = response.block();

return readers.stream()
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());

Это дает нам список , с которым мы можем работать.

Давайте углубимся в то, почему нам нужно использовать ParameterizedTypeReference .

Spring WebClient может легко десериализовать JSON в Reader.class , когда информация о типе доступна во время выполнения.

Однако с дженериками стирание типа происходит, если мы пытаемся использовать List<Reader>.class . Таким образом, Джексон не сможет определить параметр типа дженерика.

Используя ParameterizedTypeReference , мы можем решить эту проблему. Его создание как анонимного внутреннего класса использует тот факт, что подклассы универсальных классов содержат информацию о типе времени компиляции, которая не подлежит стиранию типа и может быть использована посредством отражения.

5. Вывод

В этом руководстве мы увидели три разных способа обработки объектов JSON с помощью WebClient . Мы увидели способы указания типов массивов Object и наших собственных пользовательских классов.

Затем мы узнали, как предоставить тип информации для создания списка с помощью ParameterizedTypeReference .

Как всегда, код для этой статьи доступен на GitHub .