RestTemplate Introduction
No post do blog de hoje teremos um olhar sobre o bem conhecido cliente de descanso Springs – o RestTemplate
. O RestTemplate
é a classe central dentro da estrutura Spring para executar pedidos HTTP síncronos no lado do cliente.
Like Spring JdbcTemplate, RestTemplate
é também uma API de alto nível, que por sua vez é baseada num cliente HTTP. Por defeito, a classe java.net.HttpURLConnection
do Java SDK é utilizada em RestTemplate
. No entanto, o Spring Framework torna possível mudar facilmente para outro API cliente HTTP. Como fazer isto é descrito noutro post.
p>A maioria de nós tem certamente experiência com HttpURLConnection
ou outra API cliente HTTP. Ao utilizá-la, notamos que para cada pedido é gerado repetidamente o mesmo código da placa de caldeira:
- Criar um objecto URL e abrir a ligação
- Configurar o pedido HTTP
- Executar o pedido HTTP
- Interpretação da resposta HTTP
- Converter a resposta HTTP num objecto Java
- Manipulação de excepções
Ao usar RestTemplate
todas estas coisas acontecem em segundo plano e o programador não tem de se preocupar com isso.
Começando com a Primavera 5, o Cliente Web não bloqueador e reactivo oferece uma alternativa moderna a RestTemplate
WebClient
oferece suporte tanto para pedidos HTTP síncronos e assíncronos como para cenários de streaming. Portanto, RestTemplate
será marcado como depreciado numa futura versão do Quadro de Primavera e não conterá quaisquer novas funcionalidades.
Configuração do projecto
Antes de começarmos realmente, gostaria de dar uma olhada mais atenta aos seguintes pontos da configuração do projecto:
- dependências de utilização
- classePOJO
Employee
- serviço web REST para testes
2.1 Dependências usadas
Para o projecto de RestTemplate
precisamos das seguintes dependências na nossa aplicação baseada na Spring Boot:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency></dependencies>
A Dependência spring-boot-starter-web
é um iniciador para a construção de aplicações web. Esta dependência contém a classe RestTemplate
, a opção de publicar serviços web REST e muitas outras coisas relacionadas com a web.
Como API cliente HTTP utilizamos o Apache HttpComponents para os seguintes exemplos. Lombok gera por exemplo Getter e Setter e ajuda-nos a evitar a repetição de código.
2.2 POJO Class Employee
A nossa classe POJO, que nos acompanhará através do exemplo, parece-se com isto:
@Data@NoArgsConstructor@AllArgsConstructorpublic class Employee { private long id; private String firstName; private String lastName; private long yearlyIncome;}
Obrigado a Lombok e @Data
Anotação que obtemos os métodos getter e setter gratuitamente. Além disso, @Data
gera automaticamente os seguintes métodos:
equals()
hashCode()
toString()
- Construtor com todos os campos que são anotados com
@NonNull
@NoArgsConstructor
gera um construtor sem parâmetros e @AllArgsConstructor
gera um construtor com todos os parâmetros.
2.3 REST Web Service For Testing
Para melhor compreender os seguintes exemplos, o projecto de demonstração inclui um serviço web REST muito útil. O RestController correspondente é guru.springframework.resttemplate.web.EmployeeRestController. O código para o controlador é mantido muito simples e funcional.
O serviço web REST oferece a possibilidade de criar, ler, actualizar e apagar recursos de empregados e suporta os verbos HTTP GET
POST
PUT
e DELETE
. Assim que a aplicação é interrompida, todas as alterações feitas aos recursos são perdidas. O serviço Web está disponível no ponto final http://localhost:8080/rest/employees
.
RestTemplate Methods
p> Antes de olharmos juntos para o primeiro código fonte, damos uma vista de olhos aos métodos da classe RestTemplate
. A classe fornece mais de 50 métodos, a maioria deles são sobrecarregados várias vezes. A tabela seguinte dá uma visão geral aproximada:
Método | Descrição |
void delete |
Executa a DELETE solicita e não devolve nada. |
ResponseEntity<T> exchange |
Executa um método HTTP especificado, tais como GET ou POST , e retorna um ResponseEntity que contém tanto o código de estado HTTP como o recurso como um objecto. |
T execute |
Trabalhos semelhantes a exchange , mas espera um adicional RequestCallback e um ResultSetExtractor como parâmetros. Isto é útil, por exemplo, se criar frequentemente pedidos complexos ou se quiser processar respostas complexas. |
ResponseEntity<T> getForEntity |
Executar um GET pedido e devolve um ResponseEntity que contém tanto o código de estado como o recurso como um objecto. |
T getForObject |
Trabalhos semelhantes a getForEntity , mas devolve o recurso directamente. |
HttpHeaders headForHeaders |
Executa a HEAD solicita e devolve todos os cabeçalhos HTTP para o URL especificado. |
Set<HttpMethod> optionsForAllow |
Executa um OPTIONS pedido e utiliza o cabeçalho Allow para devolver quais os métodos HTTP permitidos sob o URL especificado. |
T patchForObject |
Executar a PATCH solicitar e devolver a representação do recurso a partir da resposta. O JDK HttpURLConnection não suporta PATCH , mas Apache HttpComponents e outros suportam. |
ResponseEntity<T> postForEntity |
Execute a POST pedido e devolve um ResponseEntity que contém o código de estado assim como o recurso como um objecto. |
URI postForLocation |
Trabalha como postForEntity , mas retorna o cabeçalho Location da resposta, que indica sob qual URI o recurso recém-criado pode ser alcançado. |
T postForObject |
Trabalha como postForEntity , mas devolve o recurso directamente. |
void put |
Executa a PUT solicita e não devolve nada. |
A maior parte dos métodos estão sobrecarregados de acordo com o seguinte esquema:
- URL as
String
e parâmetros URL como VarArgs do tipoString
- URL as
String
e parâmetros URL comoMap<String, String>
- URL as
java.net.URI
sem suporte para parâmetros URL
Cada método com um tipo de retorno espera um tipo de classe genérica como parâmetro para determinar o tipo de resposta.
Demonstrações de RESTTemplate
Os exemplos seguintes mostram como podemos consumir um serviço web REST utilizando a classe RestTemplate
. Todos os exemplos seguintes estão no EmployeeRestClient
class. É um cliente simples que envolve RestTemplate
e fornece métodos relacionados com os Empregados. Como sempre, pode encontrar o código no nosso GitHub Repository.
public class EmployeeRestClient { private static final String RESOURCE_PATH = "/rest/employees"; private Logger LOG = LoggerFactory.getLogger(EmployeeRestClient.class); private String REQUEST_URI; private RestTemplate restTemplate; public EmployeeRestClient(RestTemplate restTemplate, String host, int port) { this.restTemplate = restTemplate; this.REQUEST_URI = host + ":" + port + RESOURCE_PATH; }}
até agora o EmployeeRestClient
é bastante pouco espectacular. Recebemos um exemplo do RestTemplate
do construtor. Também através dos parâmetros do construtor, obtemos o anfitrião e a porta em que o serviço web REST é executado.
Important: Todos os exemplos seguintes usam Apache HttpComponents como API cliente HTTP subjacente. Como isto pode ser configurado para o RestTemplate
é explicado no post Using RestTemplate with Apaches HttpClient.
4.1 GET
4.1.1 getForEntity()
Comecemos com um exemplo simples para consultar um único recurso:
public ResponseEntity<Employee> getForEntity(long id) { ResponseEntity<Employee> entity = restTemplate.getForEntity(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); LOG.info("Status code value: " + entity.getStatusCodeValue()); LOG.info("HTTP Header 'ContentType': " + entity.getHeaders().getContentType()); return entity;}
Neste trecho de código, usamos o método getForEntity()
, que retorna um objecto ResponseEntity
como resultado. Como parâmetro, o método espera que o URI do recurso, incluindo quaisquer marcadores de lugar e o tipo de classe para converter o corpo.
ResponseEntity
encapsula o código de estado da resposta HTTP, os cabeçalhos HTTP e o corpo que já foi convertido num objecto Java.
Em vez de consultar um único recurso, é claro que também é possível consultar uma colecção de recursos, como mostra o seguinte trecho de código:
public List<Employee> getAll(int page, int pageSize) { String requestUri = REQUEST_URI + "?page={page}&pageSize={pageSize}"; Map<String, String> urlParameters = new HashMap<>(); urlParameters.put("page", Integer.toString(page)); urlParameters.put("pageSize", Long.toString(pageSize)); ResponseEntity<Employee> entity = restTemplate.getForEntity(requestUri, Employee.class, urlParameters); return entity.getBody() != null? Arrays.asList(entity.getBody()) : Collections.emptyList();}
O serviço web REST espera um número de página e um pageSize (número de recursos por página) como parâmetros de consulta para consultar uma colecção de recursos. Para estes parâmetros, um Map
é utilizado neste trecho de código em vez de VarArgs. O ResponseEntity
é digitado num conjunto de Employee
uma vez que esperamos um número indefinido de funcionários no resultado.
4.1.2 getForObject()
Se apenas o corpo for de interesse, o método getForObject()
pode ser usado para consultar directamente o recurso como um objecto Java:
public Optional<Employee> getForObject(long id) { Employee employee = restTemplate.getForObject(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); return Optional.ofNullable(employee);}
No entanto, se quiser operar directamente na cadeia JSON, isto também é possível. Se o tipo de classe for simplesmente String.class
, obtemos a string JSON em bruto:
public JsonNode getAsJsonNode(long id) throws IOException { String jsonString = restTemplate.getForObject(REQUEST_URI + "/{id}", String.class, id); ObjectMapper mapper = new ObjectMapper(); return mapper.readTree(jsonString);}
Via ObjectMapper
podemos simplesmente transformar a cadeia JSON em JsonNode
e depois aceder aos nós individuais do JsonNode
muito confortavelmente através de jsonNode.path("fieldName")
:
@Testvoid test_getAsJsonNode() throws Exception { JsonNode jsonNode = client.getAsJsonNode(3); assertNotNull(jsonNode); assertEquals(peterGrey.getId(), jsonNode.path("id").asLong()); assertEquals(peterGrey.getFirstName(), jsonNode.path("firstName").asText()); assertEquals(peterGrey.getLastName(), jsonNode.path("lastName").asText()); assertEquals(peterGrey.getYearlyIncome(), jsonNode.path("yearlyIncome").asLong());}
4.2 POST
4.2.1 postForObject()
A criação de um novo recurso via POST
é possível com uma linha única:
public Employee postForObject(Employee newEmployee) { return restTemplate.postForObject(REQUEST_URI, newEmployee, Employee.class);}
Além do pedido URI, o método postForObject()
espera qualquer objecto que represente o corpo do pedido e um tipo de classe para a conversão da resposta. Como resposta, o serviço web REST devolve o recurso criado incluindo o ID.
4.2.2 postForLocation()
Muito semelhante a postForObject
funciona o método postForLocation()
. Aqui só obtemos o URI do novo recurso em vez do recurso criado:
public URI postForLocation(Employee newEmployee) { return restTemplate.postForLocation(REQUEST_URI, newEmployee);}
4.2.3 postForEntity()
e finalmente há postForEntity
, que devolve um ResponseEntity
. Adicionalmente, o exemplo mostra-nos como podemos enviar os nossos próprios valores no cabeçalho HTTP para o servidor:
public ResponseEntity<Employee> postForEntity(Employee newEmployee) { MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("User-Agent", "EmployeeRestClient demo class"); headers.add("Accept-Language", "en-US"); HttpEntity<Employee> entity = new HttpEntity<>(newEmployee, headers); return restTemplate.postForEntity(REQUEST_URI, entity, Employee.class);}
4.3 PUT
O método put()
é utilizado para um HTTP PUT
. O retorno do método é nulo. Podemos utilizar este método para actualizar um recurso de empregado:
public void put(Employee updatedEmployee) { restTemplate.put(REQUEST_URI + "/{id}", updatedEmployee, Long.toString(updatedEmployee.getId()));}
Contudo, há alguns casos de utilização em que gostaríamos de ter um ResponseEntity
como resposta, uma vez que isto nos dá informação sobre o código de estado HTTP e os cabeçalhos HTTP enviados pelo servidor. Neste caso, podemos utilizar o método exchange()
:
public ResponseEntity<Employee> putWithExchange(Employee updatedEmployee) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.PUT, new HttpEntity<>(updatedEmployee), Employee.class, Long.toString(updatedEmployee.getId()));}
4.4 DELETE
O método delete()
é utilizado para executar um DELETE
pedido:
public void delete(long id) { restTemplate.delete(REQUEST_URI + "/{id}", Long.toString(id));}
Aqui novamente o mesmo que com put()
. Se o código de estado HTTP ou os cabeçalhos HTTP forem de interesse, o método exchange()
deve ser usado:
public ResponseEntity<Void> deleteWithExchange(long id) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.DELETE, null, Void.class, Long.toString(id));}
Desde que o servidor não nos devolva nada, usamos Void.class
como o tipo para conversão de corpos de resposta.
4.5 HEAD
Se apenas os cabeçalhos HTTP de um pedido HTTP forem de interesse, usamos o método headForHeaders()
:
public HttpHeaders headForHeaders() { return restTemplate.headForHeaders(REQUEST_URI);}
Um teste deste método confirma que recebemos uma resposta com o tipo de conteúdo application/json
quando consultamos o URL especificado:
@Testvoid test_headForHeaders() { HttpHeaders httpHeaders = client.headForHeaders(); assertNotNull(httpHeaders.getContentType()); assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));}
4.6 OPÇÕES
Com uma consulta via HTTP OPTIONS
, podemos descobrir quais são os verbos HTTP permitidos para o URL especificado. RestTemplate
fornece o método optionsForAllow()
para isto:
public Set<HttpMethod> optionsForAllow(long id) { return restTemplate.optionsForAllow(REQUEST_URI + "/{id}", Long.toString(id));}
Um teste deste método confirma que podemos consultar o URL http://localhost:8080/rest/employees/1
com os verbos HTTP GET
PUT
e DELETE
:
@Testvoid test_optionsForAllow() { Set<HttpMethod> httpMethods = client.optionsForAllow(1); List<HttpMethod> expectedHttpMethods = List.of(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE); assertTrue(httpMethods.containsAll(expectedHttpMethods));}
Sumário
Neste post de blogue, analisámos como trabalhamos com a classe RestTemplate
. Analisámos o seguinte:
- uma visão geral do RestTemplate e os seus métodos
- umerosos exemplos de código para os seguintes verbos HTTP:
GET
POST
PUT
-
DELETE
HEAD
OPTIONS
Tambem, gostava de verificar o repositório do projecto no GitHub. Lá encontrará também uma aula de teste, que não discutimos em detalhe aqui.
I gostaria também de chamar a sua atenção para o post do blogue Using RestTemplate with Apaches HttpClient. Neste post, damos uma olhada em como configurar RestTemplate
para o utilizar com a API cliente HTTP do Apache.