Introducción a RestTemplate
En la entrada del blog de hoy vamos a echar un vistazo al conocido cliente Rest de Spring – el RestTemplate
. El RestTemplate
es la clase central dentro del framework de Spring para ejecutar peticiones HTTP síncronas en el lado del cliente.
Al igual que Spring JdbcTemplate, RestTemplate
también es una API de alto nivel, que a su vez se basa en un cliente HTTP. Por defecto, la clase java.net.HttpURLConnection
del SDK de Java se utiliza en RestTemplate
. Sin embargo, Spring Framework permite cambiar fácilmente a otra API de cliente HTTP. En otra entrada del blog se describe cómo hacerlo.
La mayoría de nosotros seguramente tenemos experiencia con HttpURLConnection
u otra API de cliente HTTP. Al usarlo nos hemos dado cuenta de que para cada petición se genera una y otra vez el mismo código boilerplate:
- Crear un objeto URL y abrir la conexión
- Configurar la petición HTTP
- Ejecutar la petición HTTP
- Interpretar la respuesta HTTP
- Convertir la respuesta HTTP en un objeto Java
- Manejo de excepciones
- Dependencias utilizadas
- Clase POJO
Employee
- Servicio web REST para pruebas
Cuando se utiliza RestTemplate
todas estas cosas suceden en segundo plano y el desarrollador no tiene que molestarse en ello.
A partir de Spring 5, el WebClient no bloqueante y reactivo ofrece una alternativa moderna a RestTemplate
WebClient
ofrece soporte para peticiones HTTP síncronas y asíncronas y escenarios de streaming. Por lo tanto, RestTemplate
será marcado como obsoleto en una futura versión de Spring Framework y no contendrá nuevas funcionalidades.
Configuración del proyecto
Antes de empezar de verdad, me gustaría ver con más detalle los siguientes puntos de la configuración del proyecto:
2.1 Dependencias utilizadas
Para el proyecto de demostración RestTemplate
necesitamos las siguientes dependencias en nuestra aplicación basada en 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>
La dependencia spring-boot-starter-web
es un starter para construir aplicaciones web. Esta dependencia contiene la clase RestTemplate
, la opción de publicar servicios web REST y muchas otras cosas relacionadas con la web.
Como API cliente HTTP utilizamos Apache HttpComponents para los siguientes ejemplos. Lombok genera por ejemplo Getter y Setter y nos ayuda a no repetir código.
2.2 Clase POJO Empleado
Nuestra clase POJO, que nos acompañará a lo largo del ejemplo, tiene el siguiente aspecto:
@Data@NoArgsConstructor@AllArgsConstructorpublic class Employee { private long id; private String firstName; private String lastName; private long yearlyIncome;}
Gracias a Lombok y a la anotación @Data
obtenemos los métodos getter y setter de forma gratuita. Además, @Data
genera los siguientes métodos automáticamente:
equals()
hashCode()
toString()
- Constructor con todos los campos que están anotados con
@NonNull
- URL como
String
y los parámetros de la URL como VarArgs de tipoString
- URL como
String
y los parámetros de la URL comoMap<String, String>
- URL como
java.net.URI
sin soporte para parámetros URL
@NoArgsConstructor
genera un constructor sin parámetros y @AllArgsConstructor
genera un constructor con todos los parámetros.
2.3 Servicio web REST para pruebas
Para entender mejor los siguientes ejemplos, el proyecto de demostración incluye un servicio web REST muy práctico. El RestController correspondiente es guru.springframework.resttemplate.web.EmployeeRestController. El código del controlador se mantiene muy simple y funcional.
El servicio web REST ofrece la posibilidad de crear, leer, actualizar y eliminar recursos de empleados y soporta los verbos HTTP GET
POST
PUT
y DELETE
. En cuanto se detiene la aplicación, se pierden todos los cambios realizados en los recursos. El servicio web está disponible en el endpoint http://localhost:8080/rest/employees
.
Métodos de RestTemplate
Antes de ver juntos el primer código fuente, echamos un vistazo a los métodos de la clase RestTemplate
. La clase proporciona más de 50 métodos, la mayoría de ellos se sobrecargan varias veces. La siguiente tabla da una visión general:
Método | Descripción |
void delete |
Ejecuta una DELETE solicitud y no devuelve nada. |
ResponseEntity<T> exchange |
Ejecuta un método HTTP especificado, como GET o POST , y devuelve un ResponseEntity que contiene tanto el código de estado HTTP como el recurso como objeto. |
T execute |
Funciona de forma similar a exchange , pero espera un RequestCallback adicional y un ResultSetExtractor como parámetros. Esto es útil, por ejemplo, si creas frecuentemente peticiones complejas o quieres procesar respuestas complejas. |
ResponseEntity<T> getForEntity |
Ejecuta un GET solicitud y devuelve un ResponseEntity que contiene tanto el código de estado como el recurso como objeto. |
T getForObject |
Funciona de forma similar a getForEntity , pero devuelve el recurso directamente. |
HttpHeaders headForHeaders |
Ejecuta una solicitud HEAD y devuelve todas las cabeceras HTTP de la URL especificada. |
Set<HttpMethod> optionsForAllow |
Ejecuta una OPTIONS solicitud y utiliza la cabecera Allow para devolver qué métodos HTTP están permitidos bajo la URL especificada. |
T patchForObject |
Ejecuta una PATCH solicitud y devuelve la representación del recurso de la respuesta. El JDK HttpURLConnection no soporta PATCH , pero Apache HttpComponents y otros sí. |
ResponseEntity<T> postForEntity |
Ejecuta un POST solicitud y devuelve un ResponseEntity que contiene el código de estado así como el recurso como objeto. |
URI postForLocation |
Funciona como postForEntity , pero devuelve la cabecera Location de la respuesta, que indica bajo qué URI se puede acceder al recurso recién creado. |
Funciona como postForEntity , pero devuelve el recurso directamente. |
|
void put |
Ejecuta una PUT solicitud y no devuelve nada. |
La mayoría de los métodos están sobrecargados según el siguiente esquema:
Cada método con tipo de retorno espera un tipo de clase genérica como parámetro para determinar el tipo de respuesta.
Demostraciones de RestTemplate
Los siguientes ejemplos muestran cómo podemos consumir un servicio web REST utilizando la clase RestTemplate
. Todos los siguientes ejemplos están en la clase EmployeeRestClient
. Es un simple cliente que envuelve RestTemplate
y proporciona métodos relacionados con los Empleados. Como siempre, puedes encontrar el código en nuestro repositorio de GitHub.
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; }}
Hasta aquí el EmployeeRestClient
es bastante poco espectacular. Obtenemos una instancia del RestTemplate
desde el constructor. También a través de los parámetros del constructor, obtenemos el host y el puerto en el que se ejecuta el servicio web REST.
Importante: Todos los ejemplos siguientes utilizan Apache HttpComponents como API de cliente HTTP subyacente. Cómo se puede configurar esto para el RestTemplate
se explica en el post Using RestTemplate with Apaches HttpClient.
4.1 GET
4.1.1 getForEntity()
Comencemos con un ejemplo sencillo para consultar un ú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;}
En este fragmento de código, utilizamos el método getForEntity()
, que devuelve un objeto ResponseEntity
como resultado. Como parámetro, el método espera la URI del recurso incluyendo cualquier marcador de posición y el tipo de clase para convertir el cuerpo.
ResponseEntity
encapsula el código de estado de la respuesta HTTP, las cabeceras HTTP y el cuerpo que ya ha sido convertido en un objeto Java.
En lugar de consultar un solo recurso, también es posible consultar una colección de recursos, como muestra el siguiente fragmento 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();}
El servicio web REST espera un número de página y un pageSize (número de recursos por página) como parámetros de consulta para consultar una colección de recursos. Para estos parámetros, en este fragmento de código se utiliza un Map
en lugar de VarArgs. El ResponseEntity
se teclea en un array de Employee
ya que esperamos un número indefinido de empleados en el resultado.
4.1.2 getForObject()
Si sólo interesa el cuerpo, se puede utilizar el método getForObject()
para consultar el recurso directamente como un objeto Java:
public Optional<Employee> getForObject(long id) { Employee employee = restTemplate.getForObject(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); return Optional.ofNullable(employee);}
No obstante, si se quiere operar directamente sobre la cadena JSON, también es posible. Si el tipo de clase es simplemente String.class
, obtendremos la cadena JSON en 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);}
A través de ObjectMapper
podemos simplemente transformar la cadena JSON en un JsonNode
y luego acceder a los nodos individuales del JsonNode
muy cómodamente a travé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()
La creación de un nuevo recurso a través de POST
es posible con un one-liner:
public Employee postForObject(Employee newEmployee) { return restTemplate.postForObject(REQUEST_URI, newEmployee, Employee.class);}
Además de la URI de la petición, el método postForObject()
espera cualquier objeto que represente el cuerpo de la petición y un tipo de clase para la conversión de la respuesta. Como respuesta, el servicio web REST devuelve el recurso creado incluyendo el ID asignado.
4.2.2 postForLocation()
Muy similar a postForObject
funciona el método postForLocation()
. Aquí sólo obtenemos la URI del nuevo recurso en lugar del recurso creado:
public URI postForLocation(Employee newEmployee) { return restTemplate.postForLocation(REQUEST_URI, newEmployee);}
4.2.3 postForEntity()
Y finalmente está postForEntity
, que devuelve un ResponseEntity
. Además, el ejemplo nos muestra cómo podemos enviar nuestros propios valores en la cabecera HTTP al 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
El método put()
se utiliza para un HTTP PUT
. El retorno del método es nulo. Podemos utilizar este método para actualizar un recurso de empleado:
public void put(Employee updatedEmployee) { restTemplate.put(REQUEST_URI + "/{id}", updatedEmployee, Long.toString(updatedEmployee.getId()));}
Sin embargo, hay algunos casos de uso en los que nos gustaría tener un ResponseEntity
como respuesta ya que esto nos da información sobre el código de estado HTTP y las cabeceras HTTP enviadas por el servidor. En este caso, podemos utilizar el 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
El método delete()
se utiliza para ejecutar una petición DELETE
:
public void delete(long id) { restTemplate.delete(REQUEST_URI + "/{id}", Long.toString(id));}
Aquí también ocurre lo mismo que con put()
. Si interesa el código de estado HTTP o las cabeceras HTTP, hay que usar el método exchange()
:
public ResponseEntity<Void> deleteWithExchange(long id) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.DELETE, null, Void.class, Long.toString(id));}
Como el servidor no nos devuelve nada, usamos Void.class
como tipo para la conversión del cuerpo de la respuesta.
4.5 HEAD
Si sólo nos interesan las cabeceras HTTP de una petición HTTP, utilizamos el método headForHeaders()
:
public HttpHeaders headForHeaders() { return restTemplate.headForHeaders(REQUEST_URI);}
Una prueba de este método confirma que recibimos una respuesta con el tipo de contenido application/json
cuando consultamos la URL especificada:
@Testvoid test_headForHeaders() { HttpHeaders httpHeaders = client.headForHeaders(); assertNotNull(httpHeaders.getContentType()); assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));}
4.6 OPCIONES
Con una consulta vía HTTP OPTIONS
, podemos averiguar qué verbos HTTP están permitidos para la URL dada. RestTemplate
proporciona el método optionsForAllow()
para ello:
public Set<HttpMethod> optionsForAllow(long id) { return restTemplate.optionsForAllow(REQUEST_URI + "/{id}", Long.toString(id));}
Una prueba de este método confirma que podemos consultar la URL http://localhost:8080/rest/employees/1
con los verbos HTTP GET
PUT
y 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));}
Resumen
En esta entrada del blog, vimos cómo trabajamos con la clase RestTemplate
. Vimos lo siguiente:
- una visión general de RestTemplate y sus métodos
- numerables ejemplos de código para los siguientes verbos HTTP:
GET
POST
PUT
DELETE
HEAD
OPTIONS
.
También, como para revisar el repositorio del proyecto en GitHub. Allí también encontrarás una clase de prueba, de la que no hablamos en detalle aquí.
También me gustaría llamar tu atención sobre el post del blog Using RestTemplate with Apaches HttpClient. En este post, echamos un vistazo a cómo configurar RestTemplate
para utilizarlo con la API de cliente HTTP de Apache.