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
  • 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 RestTemplateWebClient 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:

    • Dependencias utilizadas
    • Clase POJO Employee
    • Servicio web REST para pruebas

    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
  • @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 GETPOSTPUT 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:

    T postForObject

    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:

    • URL como String y los parámetros de la URL como VarArgs de tipo String
    • URL como String y los parámetros de la URL como Map<String, String>
    • URL como java.net.URI sin soporte para parámetros URL
    • 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 GETPUT 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.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *