RestTemplate Einführung
Im heutigen Blog-Beitrag werfen wir einen Blick auf Springs bekannten Rest-Client – das RestTemplate
. Das RestTemplate
ist die zentrale Klasse innerhalb des Spring-Frameworks, um synchrone HTTP-Requests auf der Client-Seite auszuführen.
Wie Spring JdbcTemplate ist auch das RestTemplate
eine High-Level-API, die wiederum auf einem HTTP-Client basiert. Standardmäßig wird im RestTemplate
die Klasse java.net.HttpURLConnection
aus dem Java SDK verwendet. Das Spring-Framework bietet jedoch die Möglichkeit, einfach auf eine andere HTTP-Client-API zu wechseln. Wie das geht, ist in einem anderen Blogpost beschrieben.
Die meisten von uns haben sicherlich Erfahrung mit HttpURLConnection
oder einer anderen HTTP-Client-API. Bei der Verwendung ist uns aufgefallen, dass für jede Anfrage immer wieder der gleiche Boilerplate-Code generiert wird:
- Erzeugen eines URL-Objekts und Öffnen der Verbindung
- Konfigurieren der HTTP-Anfrage
- Ausführen der HTTP-Anfrage
- Interpretation der HTTP-Antwort
- Konvertieren der HTTP-Antwort in ein Java-Objekt
- Ausnahmebehandlung
Bei der Verwendung von RestTemplate
passieren all diese Dinge im Hintergrund und der Entwickler muss sich nicht darum kümmern.
Seit Spring 5 bietet der nicht-blockierende und reaktive WebClient eine moderne Alternative zu RestTemplate
WebClient
bietet Unterstützung sowohl für synchrone als auch für asynchrone HTTP-Anfragen und Streaming-Szenarien. Daher wird RestTemplate
in einer zukünftigen Version des Spring Frameworks als veraltet markiert und wird keine neuen Funktionalitäten enthalten.
Projekt-Setup
Bevor wir richtig loslegen, möchte ich die folgenden Punkte des Projekt-Setups näher betrachten:
- Verwendete Abhängigkeiten
- POJO-Klasse
Employee
- REST-Webservice für Tests
2.1 Verwendete Abhängigkeiten
Für das RestTemplate
-Demoprojekt benötigen wir die folgenden Abhängigkeiten in unserer Spring Boot-basierten Anwendung:
<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>
Die Abhängigkeit spring-boot-starter-web
ist ein Starter für die Erstellung von Webanwendungen. Diese Dependency enthält die Klasse RestTemplate
, die Möglichkeit, REST-Web-Services zu veröffentlichen und viele andere webbezogene Dinge.
Als HTTP-Client-API verwenden wir für die folgenden Beispiele Apache HttpComponents. Lombok generiert z.B. Getter und Setter und hilft uns, wiederholten Code zu vermeiden.
2.2 POJO-Klasse Employee
Unsere POJO-Klasse, die uns durch das Beispiel begleiten wird, sieht so aus:
@Data@NoArgsConstructor@AllArgsConstructorpublic class Employee { private long id; private String firstName; private String lastName; private long yearlyIncome;}
Dank Lombok und @Data
Annotation erhalten wir die Getter- und Setter-Methoden kostenlos. Außerdem erzeugt @Data
die folgenden Methoden automatisch:
equals()
hashCode()
toString()
- Konstruktor mit allen Feldern, die annotiert sind mit
@NonNull
@NoArgsConstructor
erzeugt einen parameterlosen Konstruktor und @AllArgsConstructor
erzeugt einen Konstruktor mit allen Parametern.
2.3 REST Web Service zum Testen
Zum besseren Verständnis der folgenden Beispiele enthält das Demoprojekt einen sehr praktischen REST Web Service. Der zugehörige RestController ist guru.springframework.resttemplate.web.EmployeeRestController. Der Code für den Controller ist sehr einfach und funktional gehalten.
Der REST-Webservice bietet die Möglichkeit, Mitarbeiterressourcen anzulegen, zu lesen, zu aktualisieren und zu löschen und unterstützt die HTTP-Verben GET
POST
PUT
und DELETE
. Sobald die Anwendung gestoppt wird, gehen alle an den Ressourcen vorgenommenen Änderungen verloren. Der Webdienst ist am Endpunkt http://localhost:8080/rest/employees
verfügbar.
RestTemplate-Methoden
Bevor wir uns den ersten Quellcode gemeinsam ansehen, werfen wir einen Blick auf die Methoden der Klasse RestTemplate
. Die Klasse bietet über 50 Methoden, die meisten davon sind mehrfach überladen. Die folgende Tabelle gibt einen groben Überblick:
Methode | Beschreibung |
void delete |
Erledigt eine DELETE Anfrage und gibt nichts zurück. |
ResponseEntity<T> exchange |
Führt eine bestimmte HTTP-Methode aus, wie GET oder POST , und gibt ein ResponseEntity zurück, das sowohl den HTTP-Statuscode als auch die Ressource als Objekt enthält. |
T execute |
Wirkt ähnlich wie exchange , erwartet aber zusätzlich ein RequestCallback und ein ResultSetExtractor als Parameter. Dies ist z. B. sinnvoll, wenn Sie häufig komplexe Anfragen erstellen oder komplexe Antworten verarbeiten wollen. |
ResponseEntity<T> getForEntity |
Ausführen eines GET Anfrage aus und gibt ein ResponseEntity zurück, das sowohl den Statuscode als auch die Ressource als Objekt enthält. |
T getForObject |
Wirkt ähnlich wie getForEntity , gibt aber die Ressource direkt zurück. |
HttpHeaders headForHeaders |
Führt eine HEAD Anfrage aus und liefert alle HTTP-Header für die angegebene URL. |
Set<HttpMethod> optionsForAllow |
Ausführen eines OPTIONS Anfrage aus und verwendet den Allow Header, um zurückzugeben, welche HTTP-Methoden unter der angegebenen URL erlaubt sind. |
T patchForObject |
Führt eine PATCH Anfrage aus und gibt die Darstellung der Ressource aus der Antwort zurück. Das JDK HttpURLConnection unterstützt kein PATCH , Apache HttpComponents und andere jedoch schon. |
ResponseEntity<T> postForEntity |
Führt ein POST Anfrage aus und gibt ein ResponseEntity zurück, das den Statuscode sowie die Ressource als Objekt enthält. |
URI postForLocation |
Wirkt wie postForEntity , gibt aber den Location -Header aus der Antwort zurück, der angibt, unter welchem URI die neu erstellte Ressource zu erreichen ist. |
T postForObject |
Wirkt wie postForEntity , gibt aber die Ressource direkt zurück. |
void put |
Führt eine PUT Anfrage aus und gibt nichts zurück. |
Die meisten der Methoden sind nach folgendem Schema überladen:
- URL als
String
und URL-Parameter als VarArgs vom TypString
- URL als
String
und URL-Parameter alsMap<String, String>
- URL als
java.net.URI
ohne Unterstützung für URL-Parameter
Jede Methode mit einem Rückgabetyp erwartet einen generischen Klassentyp als Parameter, um den Typ der Antwort zu bestimmen.
RestTemplate Demonstrationen
Die folgenden Beispiele zeigen, wie wir einen REST-Webdienst mit der Klasse RestTemplate
konsumieren können. Alle folgenden Beispiele befinden sich in der Klasse EmployeeRestClient
. Es ist ein einfacher Client, der RestTemplate
umhüllt und mitarbeiterbezogene Methoden bereitstellt. Den Code finden Sie wie immer in unserem 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; }}
So weit ist das EmployeeRestClient
recht unspektakulär. Wir erhalten über den Konstruktor eine Instanz des RestTemplate
. Ebenfalls über Konstruktorparameter erhalten wir den Host und den Port, auf dem der REST-Webdienst läuft.
Wichtig: Alle folgenden Beispiele verwenden Apache HttpComponents als zugrunde liegende HTTP-Client-API. Wie diese für den RestTemplate
konfiguriert werden kann, wird im Beitrag RestTemplate mit Apaches HttpClient verwenden erklärt.
4.1 GET
4.1.1 getForEntity()
Beginnen wir mit einem einfachen Beispiel, um eine einzelne Ressource abzufragen:
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 diesem Codeschnipsel verwenden wir die Methode getForEntity()
, die als Ergebnis ein ResponseEntity
Objekt zurückgibt. Als Parameter erwartet die Methode die URI der Ressource inklusive eventueller Platzhalter und den Klassentyp für die Konvertierung des Bodys.
ResponseEntity
kapselt den Statuscode der HTTP-Antwort, die HTTP-Header und den bereits in ein Java-Objekt konvertierten Body.
Anstatt eine einzelne Ressource abzufragen, ist es natürlich auch möglich, eine Sammlung von Ressourcen abzufragen, wie der folgende Codeschnipsel zeigt:
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();}
Der REST-Webservice erwartet als Abfrageparameter für die Abfrage einer Sammlung von Ressourcen eine Seitennummer und eine pageSize (Anzahl der Ressourcen pro Seite). Für diese Parameter wird in diesem Codeschnipsel anstelle von VarArgs ein Map
verwendet. Das ResponseEntity
wird auf ein Array Employee
getippt, da wir eine unbestimmte Anzahl von Mitarbeitern im Ergebnis erwarten.
4.1.2 getForObject()
Wenn nur der Body von Interesse ist, kann die getForObject()
-Methode verwendet werden, um die Ressource direkt als Java-Objekt abzufragen:
public Optional<Employee> getForObject(long id) { Employee employee = restTemplate.getForObject(REQUEST_URI + "/{id}", Employee.class, Long.toString(id)); return Optional.ofNullable(employee);}
Wenn Sie allerdings direkt auf dem JSON-String operieren wollen, ist auch das möglich. Wenn der Klassentyp einfach String.class
ist, erhalten wir den rohen JSON-String:
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);}
Über ObjectMapper
können wir den JSON-String einfach in ein JsonNode
transformieren umwandeln und dann auf die einzelnen Knoten des JsonNode
ganz bequem über jsonNode.path("fieldName")
zugreifen:
@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()
Das Anlegen einer neuen Ressource über POST
ist mit einem Einzeiler möglich:
public Employee postForObject(Employee newEmployee) { return restTemplate.postForObject(REQUEST_URI, newEmployee, Employee.class);}
Die Methode postForObject()
erwartet neben der Request-URI ein beliebiges Objekt, das den Body der Anfrage repräsentiert, und einen Klassentyp für die Umsetzung der Antwort. Als Antwort liefert der REST-Webdienst die erstellte Ressource inklusive der zugewiesenen ID zurück.
4.2.2 postForLocation()
Sehr ähnlich wie postForObject
arbeitet die Methode postForLocation()
. Hier erhalten wir nur die URI der neuen Ressource statt der erstellten Ressource:
public URI postForLocation(Employee newEmployee) { return restTemplate.postForLocation(REQUEST_URI, newEmployee);}
4.2.3 postForEntity()
Und schließlich gibt es noch postForEntity
, das ein ResponseEntity
zurückgibt. Zusätzlich zeigt uns das Beispiel, wie wir eigene Werte im HTTP-Header an den Server senden können:
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
Die Methode put()
wird für ein HTTP-PUT
verwendet. Der Rückgabewert der Methode ist void. Wir können diese Methode verwenden, um eine Mitarbeiter-Ressource zu aktualisieren:
public void put(Employee updatedEmployee) { restTemplate.put(REQUEST_URI + "/{id}", updatedEmployee, Long.toString(updatedEmployee.getId()));}
Es gibt jedoch einige Anwendungsfälle, in denen wir ein ResponseEntity
als Antwort haben möchten, da dies uns Informationen über den HTTP-Statuscode und die vom Server mitgesendeten HTTP-Header liefert. In diesem Fall können wir die Methode exchange()
verwenden:
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
Mit der Methode delete()
wird eine DELETE
-Anfrage ausgeführt:
public void delete(long id) { restTemplate.delete(REQUEST_URI + "/{id}", Long.toString(id));}
Hier wieder das gleiche wie bei put()
. Wenn der HTTP-Statuscode oder die HTTP-Header von Interesse sind, muss die Methode exchange()
verwendet werden:
public ResponseEntity<Void> deleteWithExchange(long id) { return restTemplate.exchange(REQUEST_URI + "/{id}", HttpMethod.DELETE, null, Void.class, Long.toString(id));}
Da der Server nichts an uns zurückgibt, verwenden wir Void.class
als Typ für die Umwandlung des Antwortkörpers.
4.5 HEAD
Wenn nur die HTTP-Header einer HTTP-Anfrage von Interesse sind, verwenden wir die Methode headForHeaders()
:
public HttpHeaders headForHeaders() { return restTemplate.headForHeaders(REQUEST_URI);}
Ein Test dieser Methode bestätigt, dass wir eine Antwort mit dem Inhaltstyp application/json
erhalten, wenn wir die angegebene URL abfragen:
@Testvoid test_headForHeaders() { HttpHeaders httpHeaders = client.headForHeaders(); assertNotNull(httpHeaders.getContentType()); assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));}
4.6 OPTIONEN
Mit einer Abfrage über HTTP OPTIONS
können wir herausfinden, welche HTTP-Verben für die angegebene URL erlaubt sind. RestTemplate
stellt dafür die Methode optionsForAllow()
zur Verfügung:
public Set<HttpMethod> optionsForAllow(long id) { return restTemplate.optionsForAllow(REQUEST_URI + "/{id}", Long.toString(id));}
Ein Test dieser Methode bestätigt, dass wir die URL http://localhost:8080/rest/employees/1
mit den HTTP-Verben GET
abfragen können, PUT
und 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));}
Zusammenfassung
In diesem Blogbeitrag haben wir uns angesehen, wie wir mit der Klasse RestTemplate
arbeiten. Wir haben uns Folgendes angesehen:
- einen allgemeinen Überblick über RestTemplate und seine Methoden
- Zahlreiche Code-Beispiele für die folgenden HTTP-Verben:
GET
POST
PUT
DELETE
HEAD
OPTIONS
Auch, schauen Sie sich gerne das Projekt-Repository auf GitHub an. Dort finden Sie auch eine Testklasse, auf die wir hier nicht näher eingegangen sind.
Ich möchte Sie außerdem auf den Blogbeitrag Using RestTemplate with Apaches HttpClient aufmerksam machen. In diesem Beitrag schauen wir uns an, wie man RestTemplate
konfiguriert, um es mit der HTTP-Client-API von Apache zu verwenden.