Spring in Action Covers Spring 5-1--11 — страница 36 из 63

Можно принять спецификацию String URL с параметрами URL, указанными в списке переменных аргументов.

Можно принять спецификацию String URL с параметрами URL, указанными в Map.

В качестве спецификации URL-адреса принимается java.net.URI без поддержки параметризованных URL-адресов.

Как только вы разберетесь с 12 операциями, предоставляемыми RestTemplate, и с тем, как работает каждая с вариантами форм методов, вы сможете приступить к написанию ресурсоемких REST-клиентов.

Чтобы использовать RestTemplate, вам нужно либо создать экземпляр там, где вам это нужно

RestTemplate rest = new RestTemplate();

или вы можете объявить его как bean и внедрить его там, где вам это нужно:

@Bean

public RestTemplate restTemplate() {

 return new RestTemplate();

}

Давайте рассмотрим операции RestTemplate, рассмотрев те из них, которые поддерживают четыре основных метода HTTP: GET, PUT, DELETE и POST. Начнем с getForObject() и getForEntity() - метод GET.

7.1.1 GET (получение) ресурсов

Предположим, вы хотите получить компонент из Taco Cloud API. Предполагая, что API не поддерживает HATEOAS, вы можете использовать getForObject() для извлечения ингредиента. Например, следующий код использует RestTemplate для извлечения объекта Ingredient по его идентификатору:

public Ingredient getIngredientById(String ingredientId) {

   return rest.getForObject("http://localhost:8080/ingredients/{id}",

       Ingredient.class, ingredientId);

}

Здесь вы используете вариант getForObject(), который принимает String URL и использует список переменных для переменных URL. Параметр ingredientId, передаваемый в getForObject(), используется для заполнения элемента {id} в указанном URL. Хотя в этом примере есть только одна переменная URL, важно знать, что параметры переменных назначаются местозаполнителям в том порядке, в котором они указаны.

Второй параметр в getForObject() - это тип, к которому должен быть приведен ответ. В этом случае данные ответа (вероятно, в формате JSON) должны быть десериализованы в объект Ingredient, который будет возвращен.

Кроме того, вы можете использовать Map, чтобы указать переменные URL:

public Ingredient getIngredientById(String ingredientId) {

   Map urlVariables = new HashMap<>();

   urlVariables.put("id", ingredientId);

   return rest.getForObject("http://localhost:8080/ingredients/{id}",

       Ingredient.class, urlVariables);

}

В этом случае значение ingredientId отображается на ключ id. Когда запрос выполняется, заполнитель {id} заменяется записью map-ы, ключом которой является id.

Использование параметра URI является более сложным процессом, требующим создания объекта URI перед вызовом getForObject(). В остальном он похож на оба других варианта:

public Ingredient getIngredientById(String ingredientId) {

   Map urlVariables = new HashMap<>();

   urlVariables.put("id", ingredientId);

   URI url = UriComponentsBuilder

       .fromHttpUrl("http://localhost:8080/ingredients/{id}")

       .build(urlVariables);

   return rest.getForObject(url, Ingredient.class);

}

Здесь объект URI определен из спецификации String, а его заполнители заполнены из записей в Map, как и в предыдущем варианте getForObject(). Метод getForObject() - это простой способ извлечения ресурса. Но если клиенту нужно больше, чем содержимое body, вы можете рассмотреть возможность использования getForEntity().

getForEntity() работает почти так же, как getForObject(), но вместо возврата объекта домена, представляющего содержимым body ответа, он возвращает объект ResponseEntity, который оборачивает этот объект домена. ResponseEntity предоставляет доступ к дополнительным деталям ответа, таким как заголовки ответа.

Например, предположим, что в дополнение к данным ингредиента вы хотите посмотреть заголовок Date из ответа. С getForEntity() это становится простым:

public Ingredient getIngredientById(String ingredientId) {

   ResponseEntity responseEntity = rest.getForEntity("http://localhost:8080/ingredients/{id}",

       Ingredient.class, ingredientId);

   log.info("Fetched time: " +

       responseEntity.getHeaders().getDate());

   return responseEntity.getBody();

}

Метод getForEntity() перегружен теми же параметрами, что и getForObject(), поэтому вы можете предоставить переменные URL-адреса в качестве параметра списка переменных или вызвать getForEntity() с объектом URI.

7.1.2  PUT ресурсов

Для отправки HTTP PUT-запросов, RestTemplate предлагает метод put(). Все три перегруженных варианта put() принимают Object, который должен быть сериализован и отправлен по указанному URL. Что касается самого URL, он может быть указан как объект URI или как String. И как c getForObject() и getForEntity(), переменные URL-адреса могут быть предоставлены либо как список аргументов переменной, либо как Map.

Предположим, что вы хотите заменить ресурс ингредиента данными из нового объекта Ingredient. Следующий код должен сделать это:

public void updateIngredient(Ingredient ingredient) {

   rest.put("http://localhost:8080/ingredients/{id}",

       ingredient,

       ingredient.getId());

}

Здесь URL задан в виде строки и содержит заполнитель, который заменяется свойством id данного объекта Ingredient. Данные для отправки - это сам объект Ingredient. Метод put() возвращает void, поэтому вам ничего не нужно делать для обработки возвращаемого значения.

7.1.3 DELETE ресурсов

Предположим, что Taco Cloud больше не предлагает ингредиент и хочет полностью удалить его в качестве опции. Чтобы это произошло, вы можете вызвать метод delete() из RestTemplate:

public void deleteIngredient(Ingredient ingredient) {

   rest.delete("http://localhost:8080/ingredients/{id}",

       ingredient.getId());

}

В этом примере для delete() передаются только URL-адрес (указанный как String) и значение переменной URL-адреса. Но, как и в случае с другими методами RestTemplate, URL-адрес может быть указан как объект URI или параметры URL-адреса представлены как Map.

7.1.4 POST данных ресурсов

Теперь допустим, что вы добавили новый ингредиент в меню Taco Cloud. Это сделает HTTP POST-запрос к .../ingredients endpoint с данными ингредиентов в теле запроса. RestTemplate имеет три способа отправки запроса POST, каждый из которых имеет одинаковые перегруженные варианты для указания URL. Если вы хотите получить вновь созданный ресурс Ingredient после POST-запроса, вы должны использовать postForObject() следующим образом:

public Ingredient createIngredient(Ingredient ingredient) {

   return rest.postForObject("http://localhost:8080/ingredients",

       ingredient,

       Ingredient.class);

}

Этот вариант метода postForObject() принимает String URL спецификацию, объект, который должен быть отправлен на сервер, и доменный тип, с которым должно быть связано тело ответа. Хотя в этом случае вы не пользуетесь этим но, четвертым параметром может быть Map значения переменной URL-адреса или список переменных параметров для замены в URL.

Если для нужд клиента требуется получить ссылку на расположении только что созданного ресурса, вы можете вызвать postForLocation() :

public URI createIngredient(Ingredient ingredient) {

   return rest.postForLocation("http://localhost:8080/ingredients",ingredient);

}

Обратите внимание, что postForLocation() работает так же, как postForObject(), за исключением того, что он возвращает URI вновь созданного ресурса вместо самого объекта ресурса. Возвращенный URI получен из заголовка Location ответа. В случае, если вам понадобятся как местоположение, так и полезная нагрузка ответа, вы можете вызвать postForEntity():

public Ingredient createIngredient(Ingredient ingredient) {

  ResponseEntity responseEntity = rest.postForEntity("http://localhost:8080/ingredients",

     ingredient, Ingredient.class);

  log.info("New resource created at " +

     responseEntity.getHeaders().getLocation());

  return responseEntity.getBody();

}

Хотя методы RestTemplate отличаются по своему назначению, они очень похожи в том, как они используются. Это позволяет легко понять RestTemplate и использовать его в клиентском коде.

С другой стороны, если API, который вы используете, включает в свой ответ гиперссылки, RestTemplate не так полезен. Конечно, можно получить более подробные данные ресурса с помощью RestTemplate и работать с содержимым и ссылками, содержащимися в нем, но это не тривиально. Вместо того, чтобы бороться с использованием гипермедиа API с RestTemplate, давайте обратим наше внимание на клиентскую библиотеку, созданную для таких целей - Traverson.

7.2 Навигация REST API с помощью Traverson

Traverson поставляется с Spring Data HATEOAS как готовое решение для использования гипермедиа API в приложениях Spring. Эта библиотека на основе Java основана на аналогичной библиотеке JavaScript с тем же именем (https://github.com/traverson/traverson).

Возможно, вы заметили, что имя Трэверсон звучит как «traverse on», что является хорошим способом описать, как оно используется. В этом разделе вы будете использовать API путем обхода API по именам отношений.

Работа с Traverson начинается с создания экземпляра объекта Traverson с базовым API URI:

Traverson traverson = new Traverson(URI.create("http://localhost:8080/api"), MediaTypes.HAL_JSON);

Здесь я указал Traverson на базовый URL Taco Cloud (работает локально). Это единственный URL, который вам нужно указать Traverson-у. С этого момента вы будете перемещаться по API по именам отношений ссылок. Вы также укажете, что API будет генерировать ответы JSON с гиперссылками в стиле HAL, чтобы Traverson знал, как анализировать входящие данные ресурса. Как и RestTemplate, вы можете создать экземпляр объекта Traverson перед его использованием или объявить его как компонент, который будет введен везде, где это необходимо.