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 service Resource<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.