1. Обзор
Нашим службам часто приходится взаимодействовать с другими службами REST для получения информации.
В Spring мы можем использовать RestTemplate
для выполнения синхронных HTTP-запросов. Данные обычно возвращаются в формате JSON, и RestTemplate
может преобразовать их для нас.
В этом руководстве мы рассмотрим, как мы можем преобразовать массив JSON в три разные структуры объектов в Java :
массив объектов
, массив POJO
и список POJO
.
``
2. JSON, POJO и сервис
Давайте представим, что у нас есть конечная точка,
http://localhost:8080/users
возвращающая список пользователей в виде следующего JSON:
[{
"id": 1,
"name": "user1",
}, {
"id": 2,
"name": "user2"
}]
Нам потребуется соответствующий класс User для обработки данных:
public class User {
private int id;
private String name;
// getters and setters..
}
Для реализации нашего интерфейса мы пишем UserConsumerServiceImpl
с RestTemplate
в качестве зависимости:
public class UserConsumerServiceImpl implements UserConsumerService {
private final RestTemplate restTemplate;
public UserConsumerServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
...
}
3. Сопоставление списка объектов JSON
Когда ответ на запрос REST представляет собой массив JSON, мы можем преобразовать его в коллекцию Java несколькими способами.
Давайте посмотрим на параметры и посмотрим, насколько легко они позволяют нам обрабатывать возвращаемые данные. Мы рассмотрим извлечение имен пользователей некоторых пользовательских объектов, возвращаемых службой REST.
3.1. RestTemplate
с массивом объектов
Во-первых, давайте сделаем вызов с помощью RestTemplate.getForEntity
и используем ResponseEntity
типа Object[]
для получения ответа:
ResponseEntity<Object[]> responseEntity =
restTemplate.getForEntity(BASE_URL, Object[].class);
Далее мы можем извлечь тело в наш массив Object
:
Object[] objects = responseEntity.getBody();
Фактический объект
здесь — это просто некоторая произвольная структура, которая содержит наши данные, но не использует наш тип пользователя
. Давайте конвертируем его в наши объекты User .
Для этого нам понадобится ObjectMapper
:
ObjectMapper mapper = new ObjectMapper();
Мы можем объявить его встроенным, хотя обычно это делается как закрытый статический конечный
член класса.
Наконец, мы готовы извлечь имена пользователей:
return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, User.class))
.map(User::getName)
.collect(Collectors.toList());
С помощью этого метода мы можем по существу прочитать массив чего угодно
в массив объектов
в Java. Это может быть удобно, если мы хотим, например, только подсчитать результаты.
Однако он плохо поддается дальнейшей обработке. Нам пришлось приложить дополнительные усилия, чтобы преобразовать его в тип, с которым мы могли работать.
Десериализатор Джексона фактически десериализует JSON в серию объектов LinkedHashMap
, когда мы просим его создать Object
в качестве целевого типа. Постобработка с convertValue
— это неэффективные накладные расходы.
Мы можем избежать этого, если предоставим желаемый тип Джексону в первую очередь.
3.2. RestTemplate
с пользовательским массивом
Мы можем предоставить User[]
в RestTemplate
вместо Object[]
:
ResponseEntity<User[]> responseEntity =
restTemplate.getForEntity(BASE_URL, User[].class);
User[] userArray = responseEntity.getBody();
return Arrays.stream(userArray)
.map(User::getName)
.collect(Collectors.toList());
Мы видим, что нам больше не нужен ObjectMapper.convertValue
. Внутри ResponseEntity
есть объекты User .
Но нам все еще нужно выполнить некоторые дополнительные преобразования, чтобы использовать Java Stream
API и чтобы наш код работал со списком.
3.3. RestTemplate
со списком пользователей и ParameterizedTypeReference
Если нам нужно удобство того, что Джексон создает список
пользователей вместо
массива, нам нужно описать список
, который мы хотим создать. Для этого нам нужно использовать RestTemplate.
обмен
.
Этот метод принимает ParameterizedTypeReference
, созданный анонимным внутренним классом :
ResponseEntity<List<User>> responseEntity =
restTemplate.exchange(
BASE_URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<User>>() {}
);
List<User> users = responseEntity.getBody();
return users.stream()
.map(User::getName)
.collect(Collectors.toList());
Это создает список
, который мы хотим использовать.
Давайте подробнее рассмотрим, зачем нам нужно использовать ParameterizedTypeReference
.
В первых двух примерах Spring может легко десериализовать JSON в токен типа User.class
, где информация о типе полностью доступна во время выполнения.
Однако с дженериками стирание типа происходит, если мы пытаемся использовать List<User>.class
. Таким образом, Джексон не сможет определить тип внутри <>
.
Мы можем преодолеть это, используя токен супертипа под названием ParameterizedTypeReference
. Создание его как анонимного внутреннего класса — new ParameterizedTypeReference<List<User>>() {}
— использует тот факт, что подклассы универсальных классов содержат информацию о типах во время компиляции, которая не подлежит стиранию типа и может быть использована посредством отражения.
4. Вывод
В этой статье мы увидели три разных способа обработки объектов JSON с помощью RestTemplate
. Мы увидели, как указывать типы массивов Object
и наши собственные пользовательские классы.
Затем мы узнали, как мы предоставляем информацию о типе для создания списка
с помощью ParameterizedTypeReference
.
Как всегда, код для этой статьи доступен на GitHub .