RestTemplate Introduction
Nel post di oggi daremo uno sguardo al ben noto rest client di Spring – il RestTemplate
. Il RestTemplate
è la classe centrale all’interno del framework Spring per l’esecuzione di richieste HTTP sincrone sul lato client.
Come Spring JdbcTemplate, anche RestTemplate
è una API di alto livello, che a sua volta è basata su un client HTTP. Per impostazione predefinita, la classe java.net.HttpURLConnection
dall’SDK Java è usata in RestTemplate
. Tuttavia, lo Spring Framework permette di passare facilmente a un’altra API client HTTP. Come farlo è descritto in un altro post del blog.
La maggior parte di noi ha sicuramente esperienza con HttpURLConnection
o un’altra API client HTTP. Quando la usiamo abbiamo notato che per ogni richiesta viene generato sempre lo stesso codice boilerplate:
- Creazione di un oggetto URL e apertura della connessione
- Configurazione della richiesta HTTP
- Esecuzione della richiesta HTTP
- Interpretazione della risposta HTTP
- Conversione la risposta HTTP in un oggetto Java
- Gestione delle eccezioni
Quando si usa RestTemplate
tutte queste cose avvengono in background e lo sviluppatore non deve preoccuparsene.
A partire da Spring 5, il WebClient non bloccante e reattivo offre una moderna alternativa al RestTemplate
WebClient
offre supporto per richieste HTTP sincrone e asincrone e per scenari di streaming. Pertanto, RestTemplate
sarà contrassegnato come deprecato in una futura versione di Spring Framework e non conterrà alcuna nuova funzionalità.
Impostazione del progetto
Prima di iniziare, vorrei dare un’occhiata più da vicino ai seguenti punti dell’impostazione del progetto:
- Dipendenze utilizzate
- classePOJO
Employee
- Servizio web REST per i test
2.1 Dipendenze usate
Per il RestTemplate
progetto demo abbiamo bisogno delle seguenti dipendenze nella nostra applicazione basata su 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 dipendenza spring-boot-starter-web
è uno starter per costruire applicazioni web. Questa dipendenza contiene la classe RestTemplate
, l’opzione per pubblicare servizi web REST e molte altre cose relative al web.
Come API client HTTP usiamo Apache HttpComponents per i seguenti esempi. Lombok genera per esempio Getter e Setter e ci aiuta ad evitare di ripetere il codice.
2.2 POJO Class Employee
La nostra classe POJO, che ci accompagnerà nell’esempio, assomiglia a questa:
@Data@NoArgsConstructor@AllArgsConstructorpublic class Employee { private long id; private String firstName; private String lastName; private long yearlyIncome;}
Grazie a Lombok e all’annotazione @Data
otteniamo i metodi getter e setter gratuitamente. Inoltre, @Data
genera automaticamente i seguenti metodi:
equals()
hashCode()
toString()
- Constructor con tutti i campi che sono annotati con
@NonNull
@NoArgsConstructor
genera un costruttore senza parametri e @AllArgsConstructor
genera un costruttore con tutti i parametri.
2.3 Servizio web REST per i test
Per capire meglio gli esempi seguenti, il progetto demo include un servizio web REST molto pratico. Il RestController corrispondente è guru.springframework.resttemplate.web.EmployeeRestController. Il codice per il controller è mantenuto molto semplice e funzionale.
Il servizio web REST fornisce la possibilità di creare, leggere, aggiornare e cancellare le risorse dei dipendenti e supporta i verbi HTTP GET
POST
PUT
e DELETE
. Non appena l’applicazione viene fermata, tutte le modifiche apportate alle risorse vengono perse. Il servizio web è disponibile all’endpoint http://localhost:8080/rest/employees
.
Metodi di RestTemplate
Prima di guardare insieme il primo codice sorgente, diamo uno sguardo ai metodi della classe RestTemplate
. La classe fornisce oltre 50 metodi, la maggior parte dei quali sono sovraccaricati più volte. La seguente tabella fornisce una panoramica approssimativa:
Metodo | Descrizione |
void delete |
Esegue una DELETE richiesta e non ritorna nulla. |
ResponseEntity<T> exchange |
Esegue un metodo HTTP specificato, come GET o POST , e restituisce un ResponseEntity che contiene sia il codice di stato HTTP che la risorsa come oggetto. |
T execute |
Funziona in modo simile a exchange , ma si aspetta un ulteriore RequestCallback e un ResultSetExtractor come parametri. Questo è utile, per esempio, se si creano spesso richieste complesse o si vogliono elaborare risposte complesse. |
ResponseEntity<T> getForEntity |
Esegue un GET richiesta e restituisce un ResponseEntity che contiene sia il codice di stato che la risorsa come oggetto. |
T getForObject |
Funziona in modo simile a getForEntity , ma ritorna direttamente la risorsa. |
HttpHeaders headForHeaders |
Esegue una HEAD richiesta e restituisce tutti gli header HTTP per l’URL specificato. |
Set<HttpMethod> optionsForAllow |
Esegue una OPTIONS richiesta e utilizza l’intestazione Allow per restituire quali metodi HTTP sono consentiti nell’URL specificato. |
T patchForObject |
Esegue una PATCH richiesta e ritorna la rappresentazione della risorsa dalla risposta. Il JDK HttpURLConnection non supporta PATCH , ma Apache HttpComponents e altri lo fanno. |
ResponseEntity<T> postForEntity |
Esegue un POST richiesta e restituisce un ResponseEntity che contiene il codice di stato e la risorsa come oggetto. |
URI postForLocation |
Funziona come postForEntity , ma restituisce l’intestazione Location dalla risposta, che indica sotto quale URI la risorsa appena creata può essere raggiunta. |
T postForObject |
Funziona come postForEntity , ma ritorna direttamente la risorsa. |
void put |
Esegue una PUT richiesta e non ritorna nulla. |
La maggior parte dei metodi sono sovraccaricati secondo il seguente schema:
- URL come
String
e parametri URL come VarArgs di tipoString
- URL come
String
e parametri URL comeMap<String, String>
- URL come
java.net.URI
senza supporto per i parametri URL
Ogni metodo con un tipo di ritorno si aspetta un tipo generico di classe come parametro per determinare il tipo di risposta.
Dimostrazioni RestTemplate
I seguenti esempi mostrano come possiamo consumare un servizio web REST usando la classe RestTemplate
. Tutti gli esempi seguenti sono nella classe EmployeeRestClient
. È un semplice client che avvolge RestTemplate
e fornisce i metodi relativi a Employee. Come sempre, potete trovare il codice nel nostro repository 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; }}
Finora il EmployeeRestClient
è abbastanza poco spettacolare. Otteniamo un’istanza del RestTemplate
dal costruttore. Sempre tramite i parametri del costruttore, otteniamo l’host e la porta su cui gira il servizio web REST.
Importante: tutti i seguenti esempi usano Apache HttpComponents come sottostante API client HTTP. Come questo possa essere configurato per il RestTemplate
è spiegato nel post Using RestTemplate with Apaches HttpClient.
4.1 GET
4.1.1 getForEntity()
Iniziamo con un semplice esempio per interrogare una singola risorsa:
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;}
In questo frammento di codice, usiamo il metodo getForEntity()
, che ritorna un oggetto ResponseEntity
come risultato. Come parametro, il metodo si aspetta l’URI della risorsa compresi eventuali segnaposto e il tipo di classe per la conversione del corpo.
ResponseEntity
incapsula il codice di stato della risposta HTTP, le intestazioni HTTP e il corpo che è già stato convertito in un oggetto Java.
Invece di interrogare una singola risorsa, è ovviamente anche possibile interrogare una collezione di risorse, come mostra il seguente frammento di codice:
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();}
Il servizio web REST si aspetta un numero di pagina e un pageSize (numero di risorse per pagina) come parametri di interrogazione per interrogare una collezione di risorse. Per questi parametri, un Map
è usato in questo frammento di codice invece di VarArgs. Il ResponseEntity
è digitato in un array di Employee
poiché ci aspettiamo un numero indefinito di dipendenti nel risultato.
4.1.2 getForObject()
Se solo il corpo è di interesse, il metodo getForObject()
può essere usato per interrogare la risorsa direttamente come oggetto Java:
public Optional<Employee> getForObject(long id) { Employee employee = restTemplate.getForObject(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); return Optional.ofNullable(employee);}
Tuttavia, se volete operare direttamente sulla stringa JSON, anche questo è possibile. Se il tipo di classe è semplicemente String.class
, otteniamo la stringa JSON grezza:
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
possiamo semplicemente trasformare la stringa JSON in un JsonNode
e poi accedere ai singoli nodi del JsonNode
molto comodamente tramite 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 creazione di una nuova risorsa tramite POST
è possibile con un one-liner:
public Employee postForObject(Employee newEmployee) { return restTemplate.postForObject(REQUEST_URI, newEmployee, Employee.class);}
Oltre all’URI della richiesta, il metodo postForObject()
si aspetta un qualsiasi oggetto che rappresenta il corpo della richiesta e un tipo di classe per la conversione della risposta. Come risposta, il servizio web REST restituisce la risorsa creata incluso l’ID assegnato.
4.2.2 postForLocation()
Molto simile a postForObject
funziona il metodo postForLocation()
. Qui otteniamo solo l’URI della nuova risorsa invece della risorsa creata:
public URI postForLocation(Employee newEmployee) { return restTemplate.postForLocation(REQUEST_URI, newEmployee);}
4.2.3 postForEntity()
E infine c’è postForEntity
, che ritorna un ResponseEntity
. Inoltre, l’esempio ci mostra come possiamo inviare i nostri valori nell’intestazione HTTP al server:
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
Il metodo put()
è usato per un PUT
HTTP. Il ritorno del metodo è nullo. Possiamo usare questo metodo per aggiornare una risorsa dipendente:
public void put(Employee updatedEmployee) { restTemplate.put(REQUEST_URI + "/{id}", updatedEmployee, Long.toString(updatedEmployee.getId()));}
Tuttavia, ci sono alcuni casi d’uso in cui vorremmo avere un ResponseEntity
come risposta poiché questo ci dà informazioni sul codice di stato HTTP e sulle intestazioni HTTP inviate dal server. In questo caso, possiamo usare il metodo 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
Il metodo delete()
è usato per eseguire una richiesta DELETE
:
public void delete(long id) { restTemplate.delete(REQUEST_URI + "/{id}", Long.toString(id));}
Ancora una volta come con put()
. Se il codice di stato HTTP o le intestazioni HTTP sono interessanti, bisogna usare il metodo exchange()
:
public ResponseEntity<Void> deleteWithExchange(long id) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.DELETE, null, Void.class, Long.toString(id));}
Siccome il server non ci restituisce nulla, usiamo Void.class
come tipo di conversione del corpo della risposta.
4.5 HEAD
Se solo le intestazioni HTTP di una richiesta HTTP sono di interesse, usiamo il metodo headForHeaders()
:
public HttpHeaders headForHeaders() { return restTemplate.headForHeaders(REQUEST_URI);}
Un test di questo metodo conferma che riceviamo una risposta con il tipo di contenuto application/json
quando interroghiamo l’URL specificato:
@Testvoid test_headForHeaders() { HttpHeaders httpHeaders = client.headForHeaders(); assertNotNull(httpHeaders.getContentType()); assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));}
4.6 OPZIONI
Con una query via HTTP OPTIONS
, possiamo scoprire quali verbi HTTP sono ammessi per l’URL dato. RestTemplate
fornisce il metodo optionsForAllow()
per questo:
public Set<HttpMethod> optionsForAllow(long id) { return restTemplate.optionsForAllow(REQUEST_URI + "/{id}", Long.toString(id));}
Un test di questo metodo conferma che possiamo interrogare l’URL http://localhost:8080/rest/employees/1
con i verbi 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));}
Sommario
In questo post del blog, abbiamo visto come si lavora con la classe RestTemplate
. Abbiamo visto quanto segue:
- una panoramica generale di RestTemplate e dei suoi metodi
- numerosi esempi di codice per i seguenti verbi HTTP:
GET
POST
PUT
DELETE
HEAD
OPTIONS
Inoltre, date un’occhiata al repository del progetto su GitHub. Lì troverete anche una classe di test, che non abbiamo discusso in dettaglio qui.
Vorrei anche attirare la vostra attenzione sul post del blog Using RestTemplate with Apaches HttpClient. In questo post, diamo un’occhiata a come configurare RestTemplate
per usarlo con l’API client HTTP di Apache.