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 RestTemplateWebClient 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 GETPOSTPUT 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 DELETEAnfrage 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 HEADAnfrage 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 PATCHAnfrage 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 PUTAnfrage aus und gibt nichts zurück.

Die meisten der Methoden sind nach folgendem Schema überladen:

  • URL als String und URL-Parameter als VarArgs vom Typ String
  • URL als String und URL-Parameter als Map<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.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.