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 .