Spring-hateoas предоставляет приложениям отличный способ для создания сервисов на основе REST, которые следуют принципу HATEOAS .
Моя цель здесь состоит не в том, чтобы показать, как создать сам сервис, а в том, чтобы записать клиента в сервис.
Примером сервиса, который я собираюсь использовать, является « the spring-rest-stack », написанный Джошом Лонгом ( @starbuxman ). Конкретный подпроект, который я собираюсь использовать, это hateoas здесь . Если этот подпроект выполняется с помощью команды «mvn jetty», конечная точка на основе REST для отображения сведений о пользователе доступна по адресу http: // localhost: 8080 / users / 2, где «2» — это идентификатор пользователя и дает результат следующей структуры:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
{ "links": [{ "rel": "self", }, { "rel": "customers", }, { "rel": "photo", }], "id": 2, "firstName": "Lois", "profilePhotoMediaType": null, "lastName": "Lane", "username": "loislane", "password": null, "profilePhotoImported": false, "enabled": true, "signupDate": 1370201631000} |
Чтобы связаться с конкретным клиентом этого пользователя, конечная точка находится по адресу http: // localhost: 8080 / users / 2 / Customers / 17, что дает следующую структуру:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
{ "links": [{ "rel": "self", }, { "rel": "user", }], "id": 17, "signupDate": 1372461079000, "firstName": "Scott", "lastName": "Andrews", "databaseId": 17} |
Теперь для потребителя этих двух служб результат может быть представлен Java-типом под названием Resource в проекте Spring-hateoas и представляет собой универсальный класс со следующей сигнатурой:
|
01
02
03
04
05
06
07
08
09
10
11
|
public class Resource<T> extends ResourceSupport { protected Resource() { this.content = null; } public Resource(T content, Link... links) { this(content, Arrays.asList(links)); }... |
Таким образом, потребитель двух вышеуказанных услуг получит следующие два типа:
|
1
2
3
|
Resource<User> user = .... //call to the serviceResource<Customer> customer = ... //call to the service |
Теперь проблема в том, что, поскольку «user» и «customer» являются параметризованными типами, если бы я связывал типы, используя Джексона в качестве процессора json, я бы делал что-то вроде следующего:
|
1
2
3
|
ObjectMapper objectMapper = new ObjectMapper();Resource<Customer> customer = objectMapper.readValue(customerAsJson, Resource.class);Resource<User> user = objectMapper.readValue(userAsJson, Resource.class); |
Выше не будет работать, однако, причина в том, что из-за потери информации о параметризованном Ресурсе из-за стирания типа Java, Джексон не будет знать, чтобы создать экземпляр Resource <User> или Resource <Customer>
Исправление заключается в использовании Super Type Token , который по сути является способом предоставления информации о типе для библиотек, таких как Джексон, и я уже писал об этом ранее здесь . При этом рабочий код для сопоставления json с соответствующим параметризованным типом будет выглядеть так:
|
1
2
3
|
ObjectMapper objectMapper = new ObjectMapper();Resource<Customer> customer = objectMapper.readValue(customerAsJson, new TypeReference<Resource<Customer>>() {});Resource<User> customer = objectMapper.readValue(userAsJson, new TypeReference<Resource<User>>() {}); |
Клиентская абстракция Spring для работы со службами на основе Rest — это RestTemplate , которая может работать с различными форматами сообщений (xml, json, atom и т. Д.), Используя абстракцию HttpMessageConverter для работы со спецификой связывания для каждого из форматов сообщений.
Spring RestTemplate предоставляет собственную реализацию токена Super Type, чтобы иметь возможность привязывать различные форматы сообщений к параметризованным типам, по аналогии с типом ссылки Джексона, он называется ParameterizedTypeReference .
ParameterizedTypeReference можно использовать для точного связывания ответов Rest для пользователей и клиентов с типами Java следующим образом:
|
1
2
3
4
5
6
7
|
RestTemplate restTemplate = new RestTemplate();ResponseEntity<Resource<User>> responseEntity = restTemplate.exchange("http://localhost:8080/users/2", HttpMethod.GET, null, new ParameterizedTypeReference<Resource<User>>() {}, Collections.emptyMap());if (responseEntity.getStatusCode() == HttpStatus.OK) { Resource<User> userResource = responseEntity.getBody(); User user = userResource.getContent();} |
|
1
2
3
4
5
6
7
|
RestTemplate restTemplate = new RestTemplate();ResponseEntity<Resource<Customer>> responseEntity = restTemplate.exchange("http://localhost:8080/users/2/customers/17", HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Customer>>() {}, Collections.emptyMap());if (responseEntity.getStatusCode() == HttpStatus.OK) { Resource<Customer> customerResource = responseEntity.getBody(); Customer customer = customerResource.getContent();} |
В заключение, ParameterizedTypeReference предоставляет удобный способ работы с параметризованными типами и невероятно полезен при использовании REST-сервисов на основе Spring Hateoas.